diff --git a/Nukefile b/Nukefile index 20bddc5..4f1849d 100644 --- a/Nukefile +++ b/Nukefile @@ -5,7 +5,7 @@ (set SYSTEM ((NSString stringWithShellCommand:"uname") chomp)) (case SYSTEM ("Darwin" - (set @arch (list "x86_64")) + (set @arch (list "x86_64" )) (set @cflags "-g -std=gnu99 -fobjc-gc -DDARWIN") (set @ldflags "-framework Foundation -framework Nu -levent -lcrypto")) ("Linux" @@ -20,12 +20,15 @@ (set @framework "Nunja") (set @framework_identifier "nu.programming.nunja") (set @framework_creator_code "????") -(set @framework_extra_install - (do () (SH "sudo cp nunjad /usr/local/bin"))) +;(set @framework_extra_install (do () (SH "sudo cp nunjad /usr/local/bin"))) (compilation-tasks) (framework-tasks) +(task "clean" is + (SH "rm -rf build") + (SH "rm -rf Xcode/build")) + (task "clobber" => "clean" is (SH "rm -rf #{@framework_dir}")) diff --git a/Xcode/English.lproj/InfoPlist.strings b/Xcode/English.lproj/InfoPlist.strings new file mode 100644 index 0000000..88f65cf --- /dev/null +++ b/Xcode/English.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/Xcode/Info.plist b/Xcode/Info.plist new file mode 100644 index 0000000..4d7a357 --- /dev/null +++ b/Xcode/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + + CFBundleIdentifier + nu.programming.nunja + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + NSPrincipalClass + + + diff --git a/Xcode/Nunja.xcodeproj/project.pbxproj b/Xcode/Nunja.xcodeproj/project.pbxproj new file mode 100644 index 0000000..41fdc5a --- /dev/null +++ b/Xcode/Nunja.xcodeproj/project.pbxproj @@ -0,0 +1,459 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXBuildFile section */ + 2224951111D9BAA300A4D1F2 /* NSFileManager_Nunja.h in Headers */ = {isa = PBXBuildFile; fileRef = 2224950411D9BAA300A4D1F2 /* NSFileManager_Nunja.h */; }; + 2224951211D9BAA300A4D1F2 /* NSFileManager_Nunja.m in Sources */ = {isa = PBXBuildFile; fileRef = 2224950511D9BAA300A4D1F2 /* NSFileManager_Nunja.m */; }; + 2224951311D9BAA300A4D1F2 /* Nunja_daemonize.m in Sources */ = {isa = PBXBuildFile; fileRef = 2224950611D9BAA300A4D1F2 /* Nunja_daemonize.m */; }; + 2224951411D9BAA300A4D1F2 /* Nunja.h in Headers */ = {isa = PBXBuildFile; fileRef = 2224950711D9BAA300A4D1F2 /* Nunja.h */; }; + 2224951511D9BAA300A4D1F2 /* Nunja.m in Sources */ = {isa = PBXBuildFile; fileRef = 2224950811D9BAA300A4D1F2 /* Nunja.m */; }; + 2224951611D9BAA300A4D1F2 /* NunjaDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 2224950911D9BAA300A4D1F2 /* NunjaDelegate.h */; }; + 2224951711D9BAA300A4D1F2 /* NunjaDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 2224950A11D9BAA300A4D1F2 /* NunjaDelegate.m */; }; + 2224951811D9BAA300A4D1F2 /* NunjaRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 2224950B11D9BAA300A4D1F2 /* NunjaRequest.h */; }; + 2224951911D9BAA300A4D1F2 /* NunjaRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 2224950C11D9BAA300A4D1F2 /* NunjaRequest.m */; }; + 2224951A11D9BAA300A4D1F2 /* NunjaRequestHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 2224950D11D9BAA300A4D1F2 /* NunjaRequestHandler.h */; }; + 2224951B11D9BAA300A4D1F2 /* NunjaRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 2224950E11D9BAA300A4D1F2 /* NunjaRequestHandler.m */; }; + 2224951C11D9BAA300A4D1F2 /* NunjaRequestRouter.h in Headers */ = {isa = PBXBuildFile; fileRef = 2224950F11D9BAA300A4D1F2 /* NunjaRequestRouter.h */; }; + 2224951D11D9BAA300A4D1F2 /* NunjaRequestRouter.m in Sources */ = {isa = PBXBuildFile; fileRef = 2224951011D9BAA300A4D1F2 /* NunjaRequestRouter.m */; }; + 22C53C3911D9B17F003626DF /* nunja.nu in Resources */ = {isa = PBXBuildFile; fileRef = 22C53C3811D9B17F003626DF /* nunja.nu */; }; + 22C53C4211D9B1CE003626DF /* libevent.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 22C53C4111D9B1CE003626DF /* libevent.a */; }; + 22C53C4911D9B1FA003626DF /* Nu.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 22C53C4811D9B1FA003626DF /* Nu.framework */; }; + 22C53CCE11D9B21B003626DF /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 22C53CCD11D9B21B003626DF /* Foundation.framework */; }; + 22C53CE511D9B261003626DF /* nunjad.m in Sources */ = {isa = PBXBuildFile; fileRef = 22C53CE211D9B256003626DF /* nunjad.m */; }; + 22C53CEF11D9B2B4003626DF /* Nunja.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC2EF5B0486A6940098B216 /* Nunja.framework */; }; + 22C53D3E11D9B4A8003626DF /* NuHTTPHelpers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 22C53D3D11D9B4A8003626DF /* NuHTTPHelpers.framework */; }; + 8DC2EF530486A6940098B216 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C1666FE841158C02AAC07 /* InfoPlist.strings */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 22C53CFF11D9B2F8003626DF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8DC2EF4F0486A6940098B216; + remoteInfo = Nunja; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 089C1667FE841158C02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = ""; }; + 2224950411D9BAA300A4D1F2 /* NSFileManager_Nunja.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NSFileManager_Nunja.h; path = ../objc/NSFileManager_Nunja.h; sourceTree = SOURCE_ROOT; }; + 2224950511D9BAA300A4D1F2 /* NSFileManager_Nunja.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NSFileManager_Nunja.m; path = ../objc/NSFileManager_Nunja.m; sourceTree = SOURCE_ROOT; }; + 2224950611D9BAA300A4D1F2 /* Nunja_daemonize.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Nunja_daemonize.m; path = ../objc/Nunja_daemonize.m; sourceTree = SOURCE_ROOT; }; + 2224950711D9BAA300A4D1F2 /* Nunja.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Nunja.h; path = ../objc/Nunja.h; sourceTree = SOURCE_ROOT; }; + 2224950811D9BAA300A4D1F2 /* Nunja.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Nunja.m; path = ../objc/Nunja.m; sourceTree = SOURCE_ROOT; }; + 2224950911D9BAA300A4D1F2 /* NunjaDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NunjaDelegate.h; path = ../objc/NunjaDelegate.h; sourceTree = SOURCE_ROOT; }; + 2224950A11D9BAA300A4D1F2 /* NunjaDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NunjaDelegate.m; path = ../objc/NunjaDelegate.m; sourceTree = SOURCE_ROOT; }; + 2224950B11D9BAA300A4D1F2 /* NunjaRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NunjaRequest.h; path = ../objc/NunjaRequest.h; sourceTree = SOURCE_ROOT; }; + 2224950C11D9BAA300A4D1F2 /* NunjaRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NunjaRequest.m; path = ../objc/NunjaRequest.m; sourceTree = SOURCE_ROOT; }; + 2224950D11D9BAA300A4D1F2 /* NunjaRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NunjaRequestHandler.h; path = ../objc/NunjaRequestHandler.h; sourceTree = SOURCE_ROOT; }; + 2224950E11D9BAA300A4D1F2 /* NunjaRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NunjaRequestHandler.m; path = ../objc/NunjaRequestHandler.m; sourceTree = SOURCE_ROOT; }; + 2224950F11D9BAA300A4D1F2 /* NunjaRequestRouter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NunjaRequestRouter.h; path = ../objc/NunjaRequestRouter.h; sourceTree = SOURCE_ROOT; }; + 2224951011D9BAA300A4D1F2 /* NunjaRequestRouter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NunjaRequestRouter.m; path = ../objc/NunjaRequestRouter.m; sourceTree = SOURCE_ROOT; }; + 22C53C3811D9B17F003626DF /* nunja.nu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = nunja.nu; path = ../nu/nunja.nu; sourceTree = SOURCE_ROOT; }; + 22C53C4111D9B1CE003626DF /* libevent.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libevent.a; path = /usr/local/lib/libevent.a; sourceTree = ""; }; + 22C53C4811D9B1FA003626DF /* Nu.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nu.framework; path = /Library/Frameworks/Nu.framework; sourceTree = ""; }; + 22C53CCD11D9B21B003626DF /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; + 22C53CDE11D9B242003626DF /* nunjad */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = nunjad; sourceTree = BUILT_PRODUCTS_DIR; }; + 22C53CE211D9B256003626DF /* nunjad.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = nunjad.m; sourceTree = ""; }; + 22C53D3D11D9B4A8003626DF /* NuHTTPHelpers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NuHTTPHelpers.framework; path = /Library/Frameworks/NuHTTPHelpers.framework; sourceTree = ""; }; + 32DBCF5E0370ADEE00C91783 /* Nunja_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Nunja_Prefix.pch; sourceTree = ""; }; + 8DC2EF5A0486A6940098B216 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 8DC2EF5B0486A6940098B216 /* Nunja.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Nunja.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 22C53CDC11D9B242003626DF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 22C53CEF11D9B2B4003626DF /* Nunja.framework in Frameworks */, + 22C53D3E11D9B4A8003626DF /* NuHTTPHelpers.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8DC2EF560486A6940098B216 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 22C53C4211D9B1CE003626DF /* libevent.a in Frameworks */, + 22C53C4911D9B1FA003626DF /* Nu.framework in Frameworks */, + 22C53CCE11D9B21B003626DF /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 034768DFFF38A50411DB9C8B /* Products */ = { + isa = PBXGroup; + children = ( + 8DC2EF5B0486A6940098B216 /* Nunja.framework */, + 22C53CDE11D9B242003626DF /* nunjad */, + ); + name = Products; + sourceTree = ""; + }; + 0867D691FE84028FC02AAC07 /* Nunja */ = { + isa = PBXGroup; + children = ( + 08FB77AEFE84172EC02AAC07 /* Classes */, + 32C88DFF0371C24200C91783 /* Other Sources */, + 089C1665FE841158C02AAC07 /* Resources */, + 0867D69AFE84028FC02AAC07 /* External Frameworks and Libraries */, + 034768DFFF38A50411DB9C8B /* Products */, + ); + name = Nunja; + sourceTree = ""; + }; + 0867D69AFE84028FC02AAC07 /* External Frameworks and Libraries */ = { + isa = PBXGroup; + children = ( + 22C53CCD11D9B21B003626DF /* Foundation.framework */, + 22C53C4811D9B1FA003626DF /* Nu.framework */, + 22C53C4111D9B1CE003626DF /* libevent.a */, + 1058C7B0FEA5585E11CA2CBB /* Linked Frameworks */, + 1058C7B2FEA5585E11CA2CBB /* Other Frameworks */, + 22C53D3D11D9B4A8003626DF /* NuHTTPHelpers.framework */, + ); + name = "External Frameworks and Libraries"; + sourceTree = ""; + }; + 089C1665FE841158C02AAC07 /* Resources */ = { + isa = PBXGroup; + children = ( + 8DC2EF5A0486A6940098B216 /* Info.plist */, + 089C1666FE841158C02AAC07 /* InfoPlist.strings */, + ); + name = Resources; + sourceTree = ""; + }; + 08FB77AEFE84172EC02AAC07 /* Classes */ = { + isa = PBXGroup; + children = ( + 2224950411D9BAA300A4D1F2 /* NSFileManager_Nunja.h */, + 2224950511D9BAA300A4D1F2 /* NSFileManager_Nunja.m */, + 2224950611D9BAA300A4D1F2 /* Nunja_daemonize.m */, + 2224950711D9BAA300A4D1F2 /* Nunja.h */, + 2224950811D9BAA300A4D1F2 /* Nunja.m */, + 2224950911D9BAA300A4D1F2 /* NunjaDelegate.h */, + 2224950A11D9BAA300A4D1F2 /* NunjaDelegate.m */, + 2224950B11D9BAA300A4D1F2 /* NunjaRequest.h */, + 2224950C11D9BAA300A4D1F2 /* NunjaRequest.m */, + 2224950D11D9BAA300A4D1F2 /* NunjaRequestHandler.h */, + 2224950E11D9BAA300A4D1F2 /* NunjaRequestHandler.m */, + 2224950F11D9BAA300A4D1F2 /* NunjaRequestRouter.h */, + 2224951011D9BAA300A4D1F2 /* NunjaRequestRouter.m */, + ); + name = Classes; + sourceTree = ""; + }; + 1058C7B0FEA5585E11CA2CBB /* Linked Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = "Linked Frameworks"; + sourceTree = ""; + }; + 1058C7B2FEA5585E11CA2CBB /* Other Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = "Other Frameworks"; + sourceTree = ""; + }; + 32C88DFF0371C24200C91783 /* Other Sources */ = { + isa = PBXGroup; + children = ( + 22C53CE211D9B256003626DF /* nunjad.m */, + 22C53C3811D9B17F003626DF /* nunja.nu */, + 32DBCF5E0370ADEE00C91783 /* Nunja_Prefix.pch */, + ); + name = "Other Sources"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 8DC2EF500486A6940098B216 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 2224951111D9BAA300A4D1F2 /* NSFileManager_Nunja.h in Headers */, + 2224951411D9BAA300A4D1F2 /* Nunja.h in Headers */, + 2224951611D9BAA300A4D1F2 /* NunjaDelegate.h in Headers */, + 2224951811D9BAA300A4D1F2 /* NunjaRequest.h in Headers */, + 2224951A11D9BAA300A4D1F2 /* NunjaRequestHandler.h in Headers */, + 2224951C11D9BAA300A4D1F2 /* NunjaRequestRouter.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 22C53CDD11D9B242003626DF /* nunjad */ = { + isa = PBXNativeTarget; + buildConfigurationList = 22C53CE411D9B256003626DF /* Build configuration list for PBXNativeTarget "nunjad" */; + buildPhases = ( + 22C53CDB11D9B242003626DF /* Sources */, + 22C53CDC11D9B242003626DF /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 22C53D0011D9B2F8003626DF /* PBXTargetDependency */, + ); + name = nunjad; + productName = nunjad; + productReference = 22C53CDE11D9B242003626DF /* nunjad */; + productType = "com.apple.product-type.tool"; + }; + 8DC2EF4F0486A6940098B216 /* Nunja */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DEB91AD08733DA50010E9CD /* Build configuration list for PBXNativeTarget "Nunja" */; + buildPhases = ( + 8DC2EF500486A6940098B216 /* Headers */, + 8DC2EF520486A6940098B216 /* Resources */, + 8DC2EF540486A6940098B216 /* Sources */, + 8DC2EF560486A6940098B216 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Nunja; + productInstallPath = "$(HOME)/Library/Frameworks"; + productName = Nunja; + productReference = 8DC2EF5B0486A6940098B216 /* Nunja.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 0867D690FE84028FC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 1DEB91B108733DA50010E9CD /* Build configuration list for PBXProject "Nunja" */; + compatibilityVersion = "Xcode 3.1"; + hasScannedForEncodings = 1; + mainGroup = 0867D691FE84028FC02AAC07 /* Nunja */; + productRefGroup = 034768DFFF38A50411DB9C8B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8DC2EF4F0486A6940098B216 /* Nunja */, + 22C53CDD11D9B242003626DF /* nunjad */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8DC2EF520486A6940098B216 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8DC2EF530486A6940098B216 /* InfoPlist.strings in Resources */, + 22C53C3911D9B17F003626DF /* nunja.nu in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 22C53CDB11D9B242003626DF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 22C53CE511D9B261003626DF /* nunjad.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8DC2EF540486A6940098B216 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2224951211D9BAA300A4D1F2 /* NSFileManager_Nunja.m in Sources */, + 2224951311D9BAA300A4D1F2 /* Nunja_daemonize.m in Sources */, + 2224951511D9BAA300A4D1F2 /* Nunja.m in Sources */, + 2224951711D9BAA300A4D1F2 /* NunjaDelegate.m in Sources */, + 2224951911D9BAA300A4D1F2 /* NunjaRequest.m in Sources */, + 2224951B11D9BAA300A4D1F2 /* NunjaRequestHandler.m in Sources */, + 2224951D11D9BAA300A4D1F2 /* NunjaRequestRouter.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 22C53D0011D9B2F8003626DF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8DC2EF4F0486A6940098B216 /* Nunja */; + targetProxy = 22C53CFF11D9B2F8003626DF /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 089C1666FE841158C02AAC07 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 089C1667FE841158C02AAC07 /* English */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 1DEB91AE08733DA50010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = NO; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_MODEL_TUNING = G5; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = Nunja_Prefix.pch; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(HOME)/Library/Frameworks"; + PRODUCT_NAME = Nunja; + WRAPPER_EXTENSION = framework; + }; + name = Debug; + }; + 1DEB91AF08733DA50010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_MODEL_TUNING = G5; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = Nunja_Prefix.pch; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(HOME)/Library/Frameworks"; + PRODUCT_NAME = Nunja; + WRAPPER_EXTENSION = framework; + }; + name = Release; + }; + 1DEB91B208733DA50010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = DARWIN; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = /usr/local/include; + ONLY_ACTIVE_ARCH = YES; + PREBINDING = NO; + SDKROOT = macosx10.6; + }; + name = Debug; + }; + 1DEB91B308733DA50010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_PREPROCESSOR_DEFINITIONS = DARWIN; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = /usr/local/include; + PREBINDING = NO; + SDKROOT = macosx10.6; + }; + name = Release; + }; + 22C53CE011D9B242003626DF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_MODEL_TUNING = G5; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h"; + INSTALL_PATH = /usr/local/bin; + OTHER_LDFLAGS = ( + "-framework", + Foundation, + "-framework", + AppKit, + ); + PREBINDING = NO; + PRODUCT_NAME = nunjad; + }; + name = Debug; + }; + 22C53CE111D9B242003626DF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_MODEL_TUNING = G5; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h"; + INSTALL_PATH = /usr/local/bin; + OTHER_LDFLAGS = ( + "-framework", + Foundation, + "-framework", + AppKit, + ); + PREBINDING = NO; + PRODUCT_NAME = nunjad; + ZERO_LINK = NO; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB91AD08733DA50010E9CD /* Build configuration list for PBXNativeTarget "Nunja" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB91AE08733DA50010E9CD /* Debug */, + 1DEB91AF08733DA50010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DEB91B108733DA50010E9CD /* Build configuration list for PBXProject "Nunja" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB91B208733DA50010E9CD /* Debug */, + 1DEB91B308733DA50010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 22C53CE411D9B256003626DF /* Build configuration list for PBXNativeTarget "nunjad" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 22C53CE011D9B242003626DF /* Debug */, + 22C53CE111D9B242003626DF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 0867D690FE84028FC02AAC07 /* Project object */; +} diff --git a/Xcode/Nunja_Prefix.pch b/Xcode/Nunja_Prefix.pch new file mode 100644 index 0000000..e69de29 diff --git a/Xcode/nunjad.m b/Xcode/nunjad.m new file mode 100644 index 0000000..3183c90 --- /dev/null +++ b/Xcode/nunjad.m @@ -0,0 +1,36 @@ +#import +#import "Nunja.h" +#import "NunjaDelegate.h" +#import "NunjaRequest.h" +#import "NunjaRequestHandler.h" + +@interface MyNunjaDelegate : NunjaDelegate +{ +} +@end + +@implementation MyNunjaDelegate + +- (void) nunjaDidFinishLaunching { +#ifdef DARWIN + [self addHandlerWithHTTPMethod:@"GET" + path:@"/block/me:" + block:^(NunjaRequest *REQUEST) { + NSMutableString *result = [NSMutableString string]; + [result appendString:@"Handling 'block'\n"]; + [result appendString:@"Bindings\n"]; + [result appendString:[[REQUEST bindings] description]]; + [result appendString:@"\n"]; + [result appendString:@"Query\n"]; + [result appendString:[[REQUEST query] description]]; + [REQUEST setContentType:@"text/plain"]; + return result; + }]; +#endif +} +@end + +int main (int argc, const char * argv[]) +{ + return NunjaMain(argc, argv, @"MyNunjaDelegate"); +} diff --git a/nu/markup.nu b/nu/markup.nu deleted file mode 100644 index 323cf46..0000000 --- a/nu/markup.nu +++ /dev/null @@ -1,56 +0,0 @@ -;; requires Nunja -(load "Nunja") - -(set &html (NunjaMarkupOperator operatorWithTag:"html" prefix:<<-END - -END)) - -(global XMLNS "http://www.w3.org/1999/xhtml") - -(macro markup (*names) - `(progn - (',*names each: - (do (name) - (set stringName (name stringValue)) - (set expression - (list 'global ((+ "&" stringName) symbolValue) '(NunjaMarkupOperator operatorWithTag:stringName))) - (eval expression))))) - -# add tags as needed -(markup a - body - br - button - div - fieldset - form - head - h1 - h2 - h3 - h4 - h5 - h6 - img - input - label - li - link - meta - ol - option - p - pre - script - select - span - strong - style - table - td - textarea - th - title - tr - tbody - ul) diff --git a/nu/nunja.nu b/nu/nunja.nu index f5728c5..23cd2af 100644 --- a/nu/nunja.nu +++ b/nu/nunja.nu @@ -14,19 +14,12 @@ ;; See the License for the specific language governing permissions and ;; limitations under the License. -;; import some useful C functions -(global random (NuBridgedFunction functionWithName:"random" signature:"l")) -(global srandom (NuBridgedFunction functionWithName:"srandom" signature:"vI")) - (case (set SYSTEM ((NSString stringWithShellCommand:"uname") chomp)) ("Darwin" (import Foundation)) ("Linux" (global NSLog (NuBridgedFunction functionWithName:"NSLog" signature:"v@")) (global NSUTF8StringEncoding 4)) (else nil)) -(load "Nu:template") -(load "Nunja:mime") - ;; @class NSDate ;; @discussion Extensions for Nunja. (class NSDate @@ -39,13 +32,13 @@ timeZone:(NSTimeZone localTimeZone) locale:nil)) (result appendString:((NSTimeZone localTimeZone) abbreviation)) result) - + ;; Get an RFC822-compliant representation of a date, expressed in GMT. (- (id) rfc822-GMT is (set result ((NSMutableString alloc) init)) (result appendString: (self descriptionWithCalendarFormat:"%a, %d %b %Y %H:%M:%S GMT" - timeZone:(NSTimeZone timeZoneWithName:"GMT") locale:nil)) + timeZone:(NSTimeZone timeZoneWithName:"GMT") locale:nil)) result) ;; Get an RFC1123-compliant representation of a date. @@ -66,301 +59,83 @@ (result insertString:":" atIndex:(- (result length) 2)) result)) -;; use this pattern to extract a cookie from a header -(set cookie-pattern (regex -"[ ]*([^=]*)=(.*)")) -;; @class NunjaCookie -;; @discussion A class for managing user-identifying cookies. -(class NunjaCookie is NSObject - (ivars) - (ivar-accessors) - - ;; Generate a random identifier for use in a cookie. - (+ (id) randomIdentifier is - "#{((random) stringValue)}#{((random) stringValue)}#{((random) stringValue)}#{((random) stringValue)}") - - ;; Construct a cookie for a specified user. - (+ (id) cookieForUser:(id) user is - ((self alloc) initWithUser:user - value:(self randomIdentifier) - expiration:(NSDate dateWithTimeIntervalSinceNow:3600))) - - ;; Initialize a cookie for a specified user. - (- (id) initWithUser:(id) user value:(id) value expiration:(id) expiration is - (super init) - (set @name "session") - (set @user user) - (set @value value) - (set @expiration expiration) - (set @stringValue nil) - self) - - ;; Get a string description of a cookie. - (- (id) description is - "cookie=#{@name} value=#{@value} user=#{@user} expiration=#{(@expiration rfc822)}") - - ;; Get a string value for a cookie suitable for inclusion in a response header. - (- (id) stringValue is "#{@name}=#{@value}; Expires:#{(@expiration rfc1123)}; Path=/")) - -;; @class NunjaRequest -;; @discussion A class for managing requests received by the server. -(class NunjaRequest - (ivar-accessors) - - (- (id) cookies is - (unless @_cookies - (set @_cookies - (if (set cookies ((self requestHeaders) objectForKey:"Cookie")) - (then (set cookieDictionary (dict)) - ((cookies componentsSeparatedByString:";") each: - (do (cookieDescription) - (if (set match (cookie-pattern findInString:cookieDescription)) - (cookieDictionary setObject:(match groupAtIndex:2) - forKey:(match groupAtIndex:1))))) - cookieDictionary) - (else (dict))))) - @_cookies) - - (- (id) post is - (if (Nunja verbose) - (puts "body is") - (puts ((NSString alloc) initWithData:(self body) encoding:NSUTF8StringEncoding))) - (set d (((NSString alloc) initWithData:(self body) encoding:NSUTF8StringEncoding) urlQueryDictionary)) - (if (Nunja verbose) - (puts (d description))) - d) - - (- (void) setContentType:(id)t is (self setValue:t forResponseHeader:"Content-Type")) - - (- (void) redirectToLocation:(id) location is - (self setValue:location forResponseHeader:"Location") - (self respondWithCode:303 message:"redirecting" string:"redirecting"))) - -;; An HTTP request handler. Handlers consist of an action, a pattern, and a block. -;; The action is an HTTP verb such as "get" or "post", the pattern is either an NSString -;; or a NuRegex, and the block is a NuBlock to be evaluated in the request handling. -;; Request handers are typically created using the "get" or "post" macros and are responsible -;; for setting the response headers and returning the appropriate response data, which can be -;; either raw data (in an NSData object) or a string containing HTML text. -(class NunjaRequestHandler is NSObject - (ivar (id) action (id) pattern (id) block (id) keys) - (ivars) - (ivar-accessors) - - (- (void) setValue:(id) value forKey:(id) key is - (puts "this should not get called") - (puts "#{key}: #{value}")) - - ;; Create a handler with a specified action, pattern, and block. Used internally. - (+ (id) handlerWithAction:(id)action pattern:(id)pattern block:(id)block is - ;; if the pattern is a string that has dynamic parts, turn it into a regex - (set keys nil) - (if (pattern isKindOfClass:NSString) - (set dynamic-part-regex (regex -"/:([^/]*)")) - (set tokens (dynamic-part-regex findAllInString:pattern)) - (if (tokens count) - (set keys (tokens map:(do (token) (token groupAtIndex:1)))) - (set newpattern (+ "^" (dynamic-part-regex replaceWithString:-"/([^/]*)" inString:pattern) "$")) - (set pattern (regex newpattern)))) - - (set handler ((self alloc) init)) - (handler setAction:action) - (handler setPattern:pattern) - (handler setKeys:keys) - (handler setBlock:block) - handler) - - ;; Try to match the handler against a specified action and path. Used internally. - (- (id)matchRequest:(id)request is - (if (or (eq (request command) @action) - (and (eq (request command) "HEAD") (eq @action "GET"))) - (then - (set path (request path)) - (cond ;; match against a string - ((@pattern isKindOfClass:NSString) - (eq @pattern path)) - ;; match against a regular expression - ((@pattern isKindOfClass:NuRegex) - (set match (@pattern findInString:path)) - (if match - (then - (request setMatch:match) - (if (and @keys (eq (+ (@keys count) 1) (match count))) - (set bindings (dict)) - ((@keys count) times: - (do (i) - (bindings setValue:(match groupAtIndex:(+ i 1)) - forKey:(@keys objectAtIndex:i)))) - (request setBindings:bindings)) - YES) - (else nil))) - ;; unsupported pattern type, no match - (else nil))) - ;; unsupported action, no match - (else nil))) - - (set text-html-pattern (regex "^text/html.*$")) - - ;; Handle a request. Used internally. - (- (id)handleRequest:(id)request is - (if (Nunja verbose) - (puts "handling request #{(request uri)}") - (puts "request from host #{(request remoteHost)} port #{(request remotePort)}")) - (let (body (@block request)) - (cond - ;; return without responding, this means the handler has rejected the URL - ((not body) nil) - ;; return data objects as-is - ((body isKindOfClass:NSData) ;; we should set the content type if it isn't set - (request respondWithData:body) - t) - ;; return other non-strings as their stringValues - ((not (body isKindOfClass:NSString)) - (request respondWithString:(body stringValue)) - t) - ;; if a content type is set and it isn't text/html, return string as-is - ((and (set content-type (request valueForResponseHeader:"Content-Type")) - (not (text-html-pattern findInString:content-type))) - (request respondWithString:body) - t) - ;; return string as html - (else (request setContentType:"text/html; charset=UTF-8") - (request respondWithString:body) - t)))) - - ;; Return a response redirecting the client to a new location. This method may be called from action handlers. - (- (id)redirectResponse:(id)request toLocation:(id)location is - (request setValue:location forResponseHeader:"Location") - (request respondWithCode:303 message:"redirecting" string:"redirecting"))) - -(class Nunja - ;; Return a response redirecting the client to a new location. This method may be called from action handlers. - (+ (id)redirectResponse:(id)request toLocation:(id)location is - (request setValue:location forResponseHeader:"Location") - (request respondWithCode:303 message:"redirecting" string:"redirecting"))) - -(class NunjaCache is NSObject - (ivar (id) cache) - - (set sharedCache nil) ;; closure makes this a class variable - - (+ (id) sharedCache is - (unless sharedCache (set sharedCache ((self alloc) init))) - sharedCache) - - (- (id) init is (super init) (set @cache (dict)) self) - - (- (void) setResponse:(id) response forPath:(id)path is - (puts "caching response for #{path}") - (@cache setObject:response forKey:path)) - - (- (id) handleRequest:(id) request is - (set path (request path)) - (if (set item (@cache objectForKey:path)) - (then (request respondWithString:item) - t) - (else nil)))) - -;; @class NunjaController -;; @discussion The Nunja Controller. Responsible for handling requests. -(class NunjaController is NSObject - (ivar (id) handlers (id) root (id) defaultHandler) - (ivar-accessors) - - (set privateSharedController nil) ;; private shared variable - - (+ (id) sharedController is - privateSharedController) - - (- (id) initWithSite:(id) site is - (self init) - (set @handlers (array)) - (set privateSharedController self) - (set @root site) - (load (+ site "/site.nu")) - self) - - (- (void) handleRequest:(id) request is - (set path (request path)) - (if (Nunja verbose) - (puts (+ "REQUEST " (request command) " " path "-----")) - (puts ((request requestHeaders) description))) - (request setValue:"Nunja" forResponseHeader:"Server") - - (set handled nil) - - (set handled ((NunjaCache sharedCache) handleRequest:request)) - - (unless handled ;; try using the programmed handlers - (@handlers each: - (do (handler) - (if (and (not handled) (handler matchRequest:request)) - (set handled (handler handleRequest:request)))))) - - (unless handled ;; does the path end in a '/'? If so, append index.html - (set lastCharacter (path characterAtIndex:(- (path length) 1))) - (if (eq lastCharacter '/') - (set filename (+ @root "/public" path "index.html")) - (if ((NSFileManager defaultManager) fileExistsAtPath:filename) - (set data (NSData dataWithContentsOfFile:filename)) - (request setValue:(mime-type filename) forResponseHeader:"Content-Type") - (request setValue:"max-age=3600" forResponseHeader:"Cache-Control") - (request respondWithData:data) - (set handled YES)))) - - (unless handled ;; look for a file or directory that matches the path - (set filename (+ @root "/public" path)) - (if ((NSFileManager defaultManager) fileExistsAtPath:filename) - (then - (if ((NSFileManager defaultManager) directoryExistsAtPath:filename) - (then ;; for a directory, redirect to the same path with '/' appended - (request setValue:(+ path "/") forResponseHeader:"Location") - (request respondWithCode:301 message:"moved permanently" string:"Moved Permanently") - (set handled YES)) - (else ;; for a file, send its contents - (set data (NSData dataWithContentsOfFile:filename)) - (request setValue:(mime-type filename) forResponseHeader:"Content-Type") - (request setValue:"max-age=3600" forResponseHeader:"Cache-Control") - (request respondWithData:data) - (set handled YES)))))) - - (unless handled ;; try appending .html to the path - (set filename (+ @root "/public" path ".html")) - (if ((NSFileManager defaultManager) fileExistsAtPath:filename) - (set data (NSData dataWithContentsOfFile:filename)) - (request setValue:"text/html" forResponseHeader:"Content-Type") - (request setValue:"max-age=3600" forResponseHeader:"Cache-Control") - (request respondWithData:data) - (set handled YES))) - - (unless handled - (if @defaultHandler - (then (@defaultHandler handleRequest:request)) - (else (request respondWithCode:404 message:"Not Found" string:"Not Found. You said: #{(request command)} #{(request path)}")))))) ;; Declare a get action. -(macro-1 get (pattern *body) - `(((NunjaController sharedController) handlers) - << (NunjaRequestHandler handlerWithAction:"GET" - pattern:,pattern - block:(do (REQUEST) ,@*body)))) +(global get (macro get (path *body) + `((NunjaDelegate sharedDelegate) + addHandlerWithHTTPMethod:"GET" + path:,path + block:(do (REQUEST) ,@*body)))) ;; Declare a post action. -(macro-1 post (pattern *body) - `(((NunjaController sharedController) handlers) - << (NunjaRequestHandler handlerWithAction:"POST" - pattern:,pattern - block:(do (REQUEST) ,@*body)))) +(global post (macro post (path *body) + `((NunjaDelegate sharedDelegate) + addHandlerWithHTTPMethod:"POST" + path:,path + block:(do (REQUEST) ,@*body)))) ;; Declare a 404 handler. -(macro-1 get-404 (*body) - `((NunjaController sharedController) setDefaultHandler: - (NunjaRequestHandler handlerWithAction:"GET" - pattern:nil - block:(do (REQUEST) ,@*body)))) - -;; Set the top-level directory for a site -(macro-1 root (top-level-directory) - `((NunjaController sharedController) setRoot:,top-level-directory)) - - +(global get-404 (macro get-404 (*body) + `((NunjaDelegate sharedDelegate) + setDefaultHandlerWithBlock:(do (REQUEST) ,@*body)))) + +(Nunja setMimeTypes: + (dict "ai" "application/postscript" + "asc" "text/plain" + "avi" "video/x-msvideo" + "bin" "application/octet-stream" + "bmp" "image/bmp" + "class" "application/octet-stream" + "cer" "application/pkix-cert" + "crl" "application/pkix-crl" + "crt" "application/x-x509-ca-cert" + "css" "text/css" + "dms" "application/octet-stream" + "doc" "application/msword" + "dvi" "application/x-dvi" + "eps" "application/postscript" + "etx" "text/x-setext" + "exe" "application/octet-stream" + "gif" "image/gif" + "htm" "text/html" + "html" "text/html" + "ico" "application/icon" + "ics" "text/calendar" + "jpe" "image/jpeg" + "jpeg" "image/jpeg" + "jpg" "image/jpeg" + "js" "text/javascript" + "lha" "application/octet-stream" + "lzh" "application/octet-stream" + "mobileconfig" "application/x-apple-aspen-config" + "mov" "video/quicktime" + "mpe" "video/mpeg" + "mpeg" "video/mpeg" + "mpg" "video/mpeg" + "m3u8" "application/x-mpegURL" + "pbm" "image/x-portable-bitmap" + "pdf" "application/pdf" + "pgm" "image/x-portable-graymap" + "png" "image/png" + "pnm" "image/x-portable-anymap" + "ppm" "image/x-portable-pixmap" + "ppt" "application/vnd.ms-powerpoint" + "ps" "application/postscript" + "qt" "video/quicktime" + "ras" "image/x-cmu-raster" + "rb" "text/plain" + "rd" "text/plain" + "rtf" "application/rtf" + "sgm" "text/sgml" + "sgml" "text/sgml" + "tif" "image/tiff" + "tiff" "image/tiff" + "ts" "video/MP2T" + "txt" "text/plain" + "xbm" "image/x-xbitmap" + "xls" "application/vnd.ms-excel" + "xml" "text/xml" + "xpm" "image/x-xpixmap" + "xwd" "image/x-xwindowdump" + "zip" "application/zip")) diff --git a/nunjad.nu b/nunjad similarity index 90% rename from nunjad.nu rename to nunjad index 102d5da..5cacfbd 100755 --- a/nunjad.nu +++ b/nunjad @@ -17,8 +17,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -(load "NuHTTPHelpers") (load "Nunja") +(load "NuHTTPHelpers") (set exit (NuBridgedFunction functionWithName:"exit" signature:"vi")) @@ -41,6 +41,7 @@ ;; the option(s) we need to set (set site nil) (set port 3000) +(set localOnly NO) ;; process the remaining arguments (while (< argi (argv count)) @@ -49,22 +50,23 @@ ("--site" (set argi (+ argi 1)) (set site (argv argi))) ("-p" (set argi (+ argi 1)) (set port ((argv argi) intValue))) ("--port" (set argi (+ argi 1)) (set port ((argv argi) intValue))) - ("-l" (Nunja setLocalOnly:YES)) - ("--local" (Nunja setLocalOnly:YES)) + ("-l" (set localOnly YES)) + ("--local" (set localOnly YES)) ("-v" (Nunja setVerbose:YES)) ("--verbose" (Nunja setVerbose:YES)) (else (puts (+ "unknown option: " (argv argi))) (exit -1))) (set argi (+ argi 1))) -(set n ((Nunja alloc) init)) -(if (Nunja localOnly) +(set n (Nunja nunja)) +(if localOnly (then (n bindToAddress:"127.0.0.1" port:port)) (else (n bindToAddress:"0.0.0.0" port:port))) (if site - (n setController:((NunjaController alloc) initWithSite:site))) + (n setDelegate:((NunjaDelegate alloc) initWithSite:site))) (puts (+ "Nunja is running on port " port)) (set $site site) ;; make the path to the site directory available to handlers +((n delegate) dump) (n run) diff --git a/nunjad/nunjad b/nunjad/nunjad deleted file mode 100755 index 1df5bde..0000000 Binary files a/nunjad/nunjad and /dev/null differ diff --git a/nunjad/nunjad.1 b/nunjad/nunjad.1 deleted file mode 100644 index 2967904..0000000 --- a/nunjad/nunjad.1 +++ /dev/null @@ -1,79 +0,0 @@ -.\"Modified from man(1) of FreeBSD, the NetBSD mdoc.template, and mdoc.samples. -.\"See Also: -.\"man mdoc.samples for a complete listing of options -.\"man mdoc for the short list of editing options -.\"/usr/share/misc/mdoc.template -.Dd 6/23/10 \" DATE -.Dt nunjad 1 \" Program name and manual section number -.Os Darwin -.Sh NAME \" Section Header - required - don't modify -.Nm nunjad, -.\" The following lines are read in generating the apropos(man -k) database. Use only key -.\" words here as the database is built based on the words here and in the .ND line. -.Nm Other_name_for_same_program(), -.Nm Yet another name for the same program. -.\" Use .Nm macro to designate other names for the documented program. -.Nd This line parsed for whatis database. -.Sh SYNOPSIS \" Section Header - required - don't modify -.Nm -.Op Fl abcd \" [-abcd] -.Op Fl a Ar path \" [-a path] -.Op Ar file \" [file] -.Op Ar \" [file ...] -.Ar arg0 \" Underlined argument - use .Ar anywhere to underline -arg2 ... \" Arguments -.Sh DESCRIPTION \" Section Header - required - don't modify -Use the .Nm macro to refer to your program throughout the man page like such: -.Nm -Underlining is accomplished with the .Ar macro like this: -.Ar underlined text . -.Pp \" Inserts a space -A list of items with descriptions: -.Bl -tag -width -indent \" Begins a tagged list -.It item a \" Each item preceded by .It macro -Description of item a -.It item b -Description of item b -.El \" Ends the list -.Pp -A list of flags and their descriptions: -.Bl -tag -width -indent \" Differs from above in tag removed -.It Fl a \"-a flag as a list item -Description of -a flag -.It Fl b -Description of -b flag -.El \" Ends the list -.Pp -.\" .Sh ENVIRONMENT \" May not be needed -.\" .Bl -tag -width "ENV_VAR_1" -indent \" ENV_VAR_1 is width of the string ENV_VAR_1 -.\" .It Ev ENV_VAR_1 -.\" Description of ENV_VAR_1 -.\" .It Ev ENV_VAR_2 -.\" Description of ENV_VAR_2 -.\" .El -.Sh FILES \" File used or created by the topic of the man page -.Bl -tag -width "/Users/joeuser/Library/really_long_file_name" -compact -.It Pa /usr/share/file_name -FILE_1 description -.It Pa /Users/joeuser/Library/really_long_file_name -FILE_2 description -.El \" Ends the list -.\" .Sh DIAGNOSTICS \" May not be needed -.\" .Bl -diag -.\" .It Diagnostic Tag -.\" Diagnostic informtion here. -.\" .It Diagnostic Tag -.\" Diagnostic informtion here. -.\" .El -.Sh SEE ALSO -.\" List links in ascending order by section, alphabetically within a section. -.\" Please do not reference files that do not exist without filing a bug report -.Xr a 1 , -.Xr b 1 , -.Xr c 1 , -.Xr a 2 , -.Xr b 2 , -.Xr a 3 , -.Xr b 3 -.\" .Sh BUGS \" Document known, unremedied bugs -.\" .Sh HISTORY \" Document history if command behaves in a unique manner diff --git a/nunjad/nunjad.m b/nunjad/nunjad.m deleted file mode 100644 index a88b790..0000000 --- a/nunjad/nunjad.m +++ /dev/null @@ -1,61 +0,0 @@ -#import - -@protocol NunjaProtocol -- (int) bindToAddress:(const char *) address port:(int) port; -- (BOOL) localOnly; -- (void) setLocalOnly:(BOOL) l; -- (void) setVerbose:(BOOL) v; -- (void) setController:(id) controller; -- (void) run; -@end - -int main (int argc, const char * argv[]) { - NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; - - Class Nunja = NSClassFromString(@"Nunja"); - Class NunjaController = NSClassFromString(@"NunjaController"); - - int port = 5000; - NSString *site = nil; - - int i = 0; - while (i < argc) { - if (!strcmp(argv[i], "-s") || - !strcmp(argv[i], "--site")) { - if (++i < argc) { - site = [[[NSString alloc] initWithCString:argv[i]] autorelease]; - } - } - else if (!strcmp(argv[i], "-p") || - !strcmp(argv[i], "--port")) { - if (++i < argc) { - port = atoi(argv[i]); - } - } - else if (!strcmp(argv[i], "-l") || - !strcmp(argv[i], "--local")) { - [Nunja setLocalOnly:YES]; - } - else if (!strcmp(argv[i], "-v") || - !strcmp(argv[i], "--verbose")) { - [Nunja setVerbose:YES]; - } - i++; - } - - if (!site) { - NSLog(@"Please specify a site description with the -s or --site option"); - } else { - id nunja = [[Nunja alloc] init]; - if ([Nunja localOnly]) { - [nunja bindToAddress:"127.0.0.1" port:port]; - } else { - [nunja bindToAddress:"0.0.0.0" port:port]; - } - [nunja setController:[[NunjaController alloc] initWithSite:site]]; - [nunja run]; - } - - [pool drain]; - return 0; -} diff --git a/nunjad/nunjad.xcodeproj/project.pbxproj b/nunjad/nunjad.xcodeproj/project.pbxproj deleted file mode 100644 index ac94c49..0000000 --- a/nunjad/nunjad.xcodeproj/project.pbxproj +++ /dev/null @@ -1,225 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 45; - objects = { - -/* Begin PBXBuildFile section */ - 221A336B11D286E900535B31 /* Nu.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 221A336A11D286E900535B31 /* Nu.framework */; }; - 221A336F11D2870400535B31 /* Nunja.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 221A336E11D2870400535B31 /* Nunja.framework */; }; - 8DD76F9A0486AA7600D96B5E /* nunjad.m in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* nunjad.m */; settings = {ATTRIBUTES = (); }; }; - 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; }; - 8DD76F9F0486AA7600D96B5E /* nunjad.1 in CopyFiles */ = {isa = PBXBuildFile; fileRef = C6859EA3029092ED04C91782 /* nunjad.1 */; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 8DD76F9E0486AA7600D96B5E /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 8; - dstPath = /usr/share/man/man1/; - dstSubfolderSpec = 0; - files = ( - 8DD76F9F0486AA7600D96B5E /* nunjad.1 in CopyFiles */, - ); - runOnlyForDeploymentPostprocessing = 1; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 08FB7796FE84155DC02AAC07 /* nunjad.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = nunjad.m; sourceTree = ""; }; - 08FB779EFE84155DC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; - 221A336A11D286E900535B31 /* Nu.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nu.framework; path = /Library/Frameworks/Nu.framework; sourceTree = ""; }; - 221A336E11D2870400535B31 /* Nunja.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nunja.framework; path = Library/Frameworks/Nunja.framework; sourceTree = SDKROOT; }; - 8DD76FA10486AA7600D96B5E /* nunjad */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = nunjad; sourceTree = BUILT_PRODUCTS_DIR; }; - C6859EA3029092ED04C91782 /* nunjad.1 */ = {isa = PBXFileReference; lastKnownFileType = text.man; path = nunjad.1; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 8DD76F9B0486AA7600D96B5E /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */, - 221A336B11D286E900535B31 /* Nu.framework in Frameworks */, - 221A336F11D2870400535B31 /* Nunja.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 08FB7794FE84155DC02AAC07 /* nunjad */ = { - isa = PBXGroup; - children = ( - 08FB7795FE84155DC02AAC07 /* Source */, - C6859EA2029092E104C91782 /* Documentation */, - 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */, - 1AB674ADFE9D54B511CA2CBB /* Products */, - ); - name = nunjad; - sourceTree = ""; - }; - 08FB7795FE84155DC02AAC07 /* Source */ = { - isa = PBXGroup; - children = ( - 08FB7796FE84155DC02AAC07 /* nunjad.m */, - ); - name = Source; - sourceTree = ""; - }; - 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */ = { - isa = PBXGroup; - children = ( - 08FB779EFE84155DC02AAC07 /* Foundation.framework */, - 221A336A11D286E900535B31 /* Nu.framework */, - 221A336E11D2870400535B31 /* Nunja.framework */, - ); - name = "External Frameworks and Libraries"; - sourceTree = ""; - }; - 1AB674ADFE9D54B511CA2CBB /* Products */ = { - isa = PBXGroup; - children = ( - 8DD76FA10486AA7600D96B5E /* nunjad */, - ); - name = Products; - sourceTree = ""; - }; - C6859EA2029092E104C91782 /* Documentation */ = { - isa = PBXGroup; - children = ( - C6859EA3029092ED04C91782 /* nunjad.1 */, - ); - name = Documentation; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 8DD76F960486AA7600D96B5E /* nunjad */ = { - isa = PBXNativeTarget; - buildConfigurationList = 1DEB927408733DD40010E9CD /* Build configuration list for PBXNativeTarget "nunjad" */; - buildPhases = ( - 8DD76F990486AA7600D96B5E /* Sources */, - 8DD76F9B0486AA7600D96B5E /* Frameworks */, - 8DD76F9E0486AA7600D96B5E /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = nunjad; - productInstallPath = "$(HOME)/bin"; - productName = nunjad; - productReference = 8DD76FA10486AA7600D96B5E /* nunjad */; - productType = "com.apple.product-type.tool"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 08FB7793FE84155DC02AAC07 /* Project object */ = { - isa = PBXProject; - buildConfigurationList = 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "nunjad" */; - compatibilityVersion = "Xcode 3.1"; - hasScannedForEncodings = 1; - mainGroup = 08FB7794FE84155DC02AAC07 /* nunjad */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 8DD76F960486AA7600D96B5E /* nunjad */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXSourcesBuildPhase section */ - 8DD76F990486AA7600D96B5E /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 8DD76F9A0486AA7600D96B5E /* nunjad.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 1DEB927508733DD40010E9CD /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - COPY_PHASE_STRIP = NO; - GCC_DYNAMIC_NO_PIC = NO; - GCC_ENABLE_FIX_AND_CONTINUE = YES; - GCC_MODEL_TUNING = G5; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PRECOMPILE_PREFIX_HEADER = YES; - INSTALL_PATH = /usr/local/bin; - PRODUCT_NAME = nunjad; - }; - name = Debug; - }; - 1DEB927608733DD40010E9CD /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - GCC_MODEL_TUNING = G5; - GCC_PRECOMPILE_PREFIX_HEADER = YES; - INSTALL_PATH = /usr/local/bin; - PRODUCT_NAME = nunjad; - }; - name = Release; - }; - 1DEB927908733DD40010E9CD /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - ONLY_ACTIVE_ARCH = YES; - PREBINDING = NO; - SDKROOT = macosx10.6; - }; - name = Debug; - }; - 1DEB927A08733DD40010E9CD /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - PREBINDING = NO; - SDKROOT = macosx10.6; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 1DEB927408733DD40010E9CD /* Build configuration list for PBXNativeTarget "nunjad" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 1DEB927508733DD40010E9CD /* Debug */, - 1DEB927608733DD40010E9CD /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "nunjad" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 1DEB927908733DD40010E9CD /* Debug */, - 1DEB927A08733DD40010E9CD /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; -} diff --git a/objc/NSFileManager_Nunja.h b/objc/NSFileManager_Nunja.h new file mode 100644 index 0000000..8924419 --- /dev/null +++ b/objc/NSFileManager_Nunja.h @@ -0,0 +1,23 @@ +/*! +@file NSFileManager_Nunja.h +@discussion General utilities for the Nunja web server. +@copyright Copyright (c) 2010 Neon Design Technology, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#import + +@interface NSFileManager (Nunja) +- (BOOL) directoryExistsAtPath:(NSString *) path; +@end diff --git a/objc/NSFileManager_Nunja.m b/objc/NSFileManager_Nunja.m new file mode 100644 index 0000000..849165a --- /dev/null +++ b/objc/NSFileManager_Nunja.m @@ -0,0 +1,33 @@ +/*! +@file NSFileManager_Nunja.m +@discussion General utilities for the Nunja web server. +@copyright Copyright (c) 2010 Neon Design Technology, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#import "NSFileManager_Nunja.h" + +@implementation NSFileManager (Nunja) + +- (BOOL) directoryExistsAtPath:(NSString *) path +{ + BOOL isDirectory = NO; + BOOL fileExists = [self fileExistsAtPath:path isDirectory:&isDirectory]; + if (!fileExists) + return NO; + else + return isDirectory; +} + +@end diff --git a/objc/Nunja.h b/objc/Nunja.h new file mode 100644 index 0000000..37c84ef --- /dev/null +++ b/objc/Nunja.h @@ -0,0 +1,66 @@ +/*! + @file Nunja.h + @discussion Core of the Nunja web server. + @copyright Copyright (c) 2010 Neon Design Technology, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import +#import + +@class NunjaRequest; + +@protocol NunjaDelegateProtocol +// Override this to perform Objective-C setup of your Nunja. +- (void) nunjaDidFinishLaunching; + +// Call this within nunjaDidFinishLaunching to add a handler. +- (void) addHandlerWithHTTPMethod:(NSString *)httpMethod path:(NSString *)path block:(id)block; + +// Call this within nunjaDidFinishLaunching to set the 404 handler. +- (void) setDefaultHandlerWithBlock:(id) block; + +// Override this to add your own custom request processing. You probably won't need this. +- (void) handleRequest:(NunjaRequest *)request; +@end + + +@interface Nunja : NSObject {} +// Get a Nunja instance. We only support one per process. ++ (Nunja *) nunja; + +// Control logging ++ (void) setVerbose:(BOOL) v; ++ (BOOL) verbose; + +// Known MIME types ++ (NSMutableDictionary *) mimeTypes; ++ (void) setMimeTypes:(NSMutableDictionary *) dictionary; ++ (NSString *) mimeTypeForFileWithName:(NSString *) filename; + +// The delegate performs all request handling. +- (void) setDelegate:(id) d; +- (id) delegate; + +// Bind the server to a specified address and port. +- (int) bindToAddress:(NSString *) address port:(int) port; + +// Run the server. +- (void) run; + +@end + + +// Run Nunja. Pass nil for NunjaDelegateClassName to set up your site with Nu (site.nu). +int NunjaMain(int argc, const char *argv[], NSString *NunjaDelegateClassName); diff --git a/objc/Nunja.m b/objc/Nunja.m new file mode 100644 index 0000000..24f99a5 --- /dev/null +++ b/objc/Nunja.m @@ -0,0 +1,363 @@ +/*! + @file Nunja.m + @discussion Core of the Nunja web server. + @copyright Copyright (c) 2008 Neon Design Technology, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#include +#include +#include +#include +#include +#define HTTP_SEEOTHER 303 +#define HTTP_DENIED 403 + +#include +#include +#include // inet_ntoa + +#import +#import + +#import "Nunja.h" +#import "NunjaRequest.h" +#import "NunjaDelegate.h" + +void NunjaInit() +{ + static int initialized = 0; + if (!initialized) { + initialized = 1; + [Nu loadNuFile:@"nunja" fromBundleWithIdentifier:@"nu.programming.nunja" withContext:nil]; + } +} + +BOOL verbose_nunja = NO; + +@interface ConcreteNunja : Nunja +{ + struct event_base *event_base; + struct evhttp *httpd; + id delegate; +} + +- (id) delegate; +@end + +@implementation ConcreteNunja + + ++ (void) load +{ + NunjaInit(); +} + +static void nunja_request_handler(struct evhttp_request *req, void *nunja_pointer) +{ + Nunja *nunja = (Nunja *) nunja_pointer; + id delegate = [nunja delegate]; + if (delegate) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NunjaRequest *request = [[NunjaRequest alloc] initWithNunja:nunja request:req]; + [delegate handleRequest:request]; + [request release]; + [pool release]; + } + else { + nunja_response_helper(req, HTTP_OK, @"OK", + [[NSString stringWithFormat:@"Please set the Nunja delegate.
If you are running nunjad, use the '-s' option to specify a site.
\nRequest: %s\n", + evhttp_request_uri(req)] + dataUsingEncoding:NSUTF8StringEncoding]); + } +} + +- (id) init +{ + [super init]; + event_base = event_init(); + evdns_init(); + httpd = evhttp_new(event_base); + evhttp_set_gencb(httpd, nunja_request_handler, self); + delegate = nil; + return self; +} + +- (int) bindToAddress:(NSString *) address port:(int) port +{ + return evhttp_bind_socket(httpd, [address cStringUsingEncoding:NSUTF8StringEncoding], port); +} + +- (void) run +{ + event_base_dispatch(event_base); +} + +- (void) dealloc +{ + evhttp_free(httpd); + [super dealloc]; +} + +- (id) delegate +{ + return delegate; +} + +- (void) setDelegate:(id) d +{ + [d retain]; + [delegate release]; + delegate = d; +} + +@class NuBlock; +@class NuCell; + +static void nunja_dns_gethostbyname_cb(int result, char type, int count, int ttl, void *addresses, void *arg) +{ + id address = nil; + if (result == DNS_ERR_TIMEOUT) { + fprintf(stdout, "[Timed out] "); + } + else if (result != DNS_ERR_NONE) { + fprintf(stdout, "[Error code %d] ", result); + } + else { + fprintf(stdout, "type: %d, count: %d, ttl: %d\n", type, count, ttl); + switch (type) { + case DNS_IPv4_A: + { + struct in_addr *in_addrs = addresses; + if (ttl < 0) { + // invalid resolution + } + else if (count == 0) { + // no addresses + } + else { + address = [NSString stringWithFormat:@"%s", inet_ntoa(in_addrs[0])]; + } + break; + } + case DNS_PTR: + /* may get at most one PTR */ + // this needs review. TB. + if (count == 1) + fprintf(stdout, "addresses: %s ", *(char **)addresses); + break; + default: + break; + } + } + NuBlock *block = (NuBlock *) arg; + id args = [[NSClassFromString(@"NuCell") alloc] init]; + [args setCar:address]; + [block evalWithArguments:args context:nil]; + [block release]; + [args release]; +} + +- (void) resolveDomainName:(NSString *) name andDo:(NuBlock *) block +{ + [block retain]; + evdns_resolve_ipv4([name cStringUsingEncoding:NSUTF8StringEncoding], 0, nunja_dns_gethostbyname_cb, block); +} + +void nunja_http_request_done(struct evhttp_request *req, void *arg) +{ + NSData *data = nil; + if (req->response_code != HTTP_OK) { + if (req->response_code == HTTP_SEEOTHER) { + fprintf(stdout, "REDIRECTING\n"); + //NSDictionary *headers = nunja_request_headers_helper(req); + return; // this is not handled yet. + } + fprintf(stdout, "FAILED to get OK (response = %d)\n", req->response_code); + } + else if (evhttp_find_header(req->input_headers, "Content-Type") == NULL) { + fprintf(stdout, "FAILED to find Content-Type\n"); + } + else { + data = [NSData dataWithBytes:EVBUFFER_DATA(req->input_buffer) length:EVBUFFER_LENGTH(req->input_buffer)]; + } + NuBlock *block = (NuBlock *) arg; + id args = [[NSClassFromString(@"NuCell") alloc] init]; + [args setCar:data]; + [block evalWithArguments:args context:nil]; + [block release]; + [args release]; + fprintf(stdout, "end of callback\n"); + // leaking... + //evhttp_connection_free(req->evcon); +} + +- (void) getResourceFromHost:(NSString *) host address:(NSString *) address port:(int)port path:(NSString *)path andDo:(NuBlock *) block +{ + [block retain]; + // make the connection + struct evhttp_connection *evcon = evhttp_connection_new([address cStringUsingEncoding:NSUTF8StringEncoding], port); + if (evcon == NULL) { + fprintf(stdout, "FAILED to connect\n"); + id args = [[NSClassFromString(@"NuCell") alloc] init]; + [block evalWithArguments:args context:nil]; + [block release]; + [args release]; + return; + } + // make the request + struct evhttp_request *req = evhttp_request_new(nunja_http_request_done, block); + evhttp_add_header(req->output_headers, "Host", [host cStringUsingEncoding:NSUTF8StringEncoding]); + // give ownership of the request to the connection + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, [path cStringUsingEncoding:NSUTF8StringEncoding]) == -1) { + fprintf(stdout, "FAILED to make the request \n"); + } +} + +- (void) postDataToHost:(NSString *) host address:(NSString *) address port:(int)port path:(NSString *)path data:(NSData *) data andDo:(NuBlock *) block +{ + [block retain]; + // make the connection + struct evhttp_connection *evcon = evhttp_connection_new([address cStringUsingEncoding:NSUTF8StringEncoding], port); + if (evcon == NULL) { + fprintf(stdout, "FAILED to connect\n"); + id args = [[NSClassFromString(@"NuCell") alloc] init]; + [block evalWithArguments:args context:nil]; + [block release]; + [args release]; + return; + } + // make the request + struct evhttp_request *req = evhttp_request_new(nunja_http_request_done, block); + evhttp_add_header(req->output_headers, "Host", [host cStringUsingEncoding:NSUTF8StringEncoding]); + evhttp_add_header(req->output_headers, "Content-Length", [[NSString stringWithFormat:@"%d", [data length]] cStringUsingEncoding:NSUTF8StringEncoding]); + evhttp_add_header(req->output_headers, "Content-Type", "application/x-www-form-urlencoded"); + evbuffer_add(req->output_buffer, [data bytes], [data length]); + + // give ownership of the request to the connection + if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, [path cStringUsingEncoding:NSUTF8StringEncoding]) == -1) { + fprintf(stdout, "FAILED to make the request \n"); + } +} + +@end + +@implementation Nunja + ++ (Nunja *) nunja { + return [[[ConcreteNunja alloc] init] autorelease]; +} + ++ (void) setVerbose:(BOOL) v +{ + verbose_nunja = v; +} + ++ (BOOL) verbose {return verbose_nunja;} + +- (void) run { +} + +- (int) bindToAddress:(NSString *) address port:(int) port { + return 0; +} + +- (void) setDelegate:(id) d +{ +} + +- (id) delegate { + return nil; +} + +static NSMutableDictionary *mimeTypes = nil; + ++ (NSMutableDictionary *) mimeTypes { + return mimeTypes; +} + ++ (void) setMimeTypes:(NSMutableDictionary *) dictionary { + [dictionary retain]; + [mimeTypes release]; + mimeTypes = dictionary; +} + ++ (NSString *) mimeTypeForFileWithName:(NSString *) pathName { + if (mimeTypes) { + NSString *suffix = [[pathName componentsSeparatedByString:@"."] lastObject]; + NSString *mimeType = [mimeTypes objectForKey:suffix]; + if (mimeType) + return mimeType; + } + // default + return @"text/html; charset=utf-8"; +} + +@end + +int NunjaMain(int argc, const char *argv[], NSString *NunjaDelegateClassName) +{ + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + + int port = 5000; + NSString *site = @"."; + + BOOL localOnly = NO; + int i = 0; + while (i < argc) { + if (!strcmp(argv[i], "-s") || + !strcmp(argv[i], "--site")) { + if (++i < argc) { + site = [[[NSString alloc] initWithCString:argv[i]] autorelease]; + } + } + else if (!strcmp(argv[i], "-p") || + !strcmp(argv[i], "--port")) { + if (++i < argc) { + port = atoi(argv[i]); + } + } + else if (!strcmp(argv[i], "-l") || + !strcmp(argv[i], "--local")) { + localOnly = YES; + } + else if (!strcmp(argv[i], "-v") || + !strcmp(argv[i], "--verbose")) { + [Nunja setVerbose:YES]; + } + i++; + } + + Nunja *nunja = [Nunja nunja]; + if (localOnly) { + [nunja bindToAddress:@"127.0.0.1" port:port]; + } + else { + [nunja bindToAddress:@"0.0.0.0" port:port]; + } + Class NunjaDelegateClass = NunjaDelegateClassName ? NSClassFromString(NunjaDelegateClassName) : [NunjaDelegate class]; + id delegate = [[[NunjaDelegateClass alloc] initWithSite:site] autorelease]; + if ([delegate respondsToSelector:@selector(nunjaDidFinishLaunching)]) { + [delegate nunjaDidFinishLaunching]; + } + if ([Nunja verbose]) { + [delegate dump]; + } + [nunja setDelegate:delegate]; + [nunja run]; + + [pool drain]; + return 0; +} + diff --git a/objc/NunjaDelegate.h b/objc/NunjaDelegate.h new file mode 100644 index 0000000..24f5d7a --- /dev/null +++ b/objc/NunjaDelegate.h @@ -0,0 +1,23 @@ +#import +#import "Nunja.h" + +@class NunjaRequestHandler; +@class NunjaRequestRouter; + +@interface NunjaDelegate : NSObject +{ + NunjaRequestHandler *defaultHandler; + NunjaRequestRouter *router; +} + +- (id) initWithSite:(NSString *) site; + +- (void) addHandler:(NunjaRequestHandler *) handler; +- (void) addHandlerWithHTTPMethod:(NSString *)httpMethod path:(NSString *)path block:(id)block; +- (void) setDefaultHandlerWithBlock:(id) block; + +- (void) dump; + +@end + + diff --git a/objc/NunjaDelegate.m b/objc/NunjaDelegate.m new file mode 100644 index 0000000..43e0bcf --- /dev/null +++ b/objc/NunjaDelegate.m @@ -0,0 +1,170 @@ + +#import "NunjaDelegate.h" +#import "NunjaRequestRouter.h" +#import "NunjaRequestHandler.h" +#import "Nunja.h" + +@implementation NunjaDelegate + +static NunjaDelegate *_sharedDelegate; + ++ (NunjaDelegate *) sharedDelegate { + return _sharedDelegate; +} + +- (id) init +{ + if (self = [super init]) { + self->router = [[NunjaRequestRouter routerWithToken:@""] retain]; + } + _sharedDelegate = self; + return self; +} + +- (id) initWithSite:(id) site +{ + self = [self init]; + + id parser = [NSClassFromString(@"Nu") parser]; + + // set working directory to site path + chdir([site cStringUsingEncoding:NSUTF8StringEncoding]); + + // load site description + NSString *filename = [NSString stringWithFormat:@"site.nu", site]; + NSString *sourcecode = [NSString stringWithContentsOfFile:filename]; + if (sourcecode) { + [parser parseEval:sourcecode]; + } + return self; +} + +- (void) setDefaultHandlerWithBlock:(id) block { + id handler = [NunjaRequestHandler handlerWithHTTPMethod:@"GET" path:@"" block:block]; + [handler retain]; + [self->defaultHandler release]; + self->defaultHandler = handler; +} + +- (void) addHandler:(id) handler +{ + [self->router insertHandler:handler level:0]; +} + +- (void) addHandlerWithHTTPMethod:(NSString *)httpMethod path:(NSString *)path block:(id)block +{ + [self addHandler:[NunjaRequestHandler handlerWithHTTPMethod:httpMethod path:path block:block]]; +} + +- (void) dump +{ + [self->router dump:0]; +} + +- (void) handleRequest:(NunjaRequest *) request +{ + id path = [request path]; + if ([Nunja verbose]) { + NSLog(@"REQUEST %@ %@ ----", [request HTTPMethod], path); + NSLog(@"%@", [request requestHeaders]); + } + [request setValue:@"Nunja" forResponseHeader:@"Server"]; + [request setBindings:[NSMutableDictionary dictionary]]; + + id httpMethod = [request HTTPMethod]; + if ([httpMethod isEqualToString:@"HEAD"]) + httpMethod = @"GET"; + + id parts = [[NSString stringWithFormat:@"%@%@", httpMethod, [request path]] componentsSeparatedByString:@"/"]; + if (([parts count] > 2) && [[parts lastObject] isEqualToString:@""]) { + parts = [parts subarrayWithRange:NSMakeRange(0, [parts count]-1)]; + } + + BOOL handled = NO; // [[NunjaCache sharedCache] handleRequest:request]; + + if (!handled) { + NunjaRequestHandler *handler = [router routeRequest:request parts:parts level:0]; + if (handler) { + handled = [handler handleRequest:request]; + } + } + + if (!handled) { // does the path end in a '/'? If so, append index.html + unichar lastCharacter = [path characterAtIndex:[path length] - 1]; + if (lastCharacter == '/') { + if (!handled) { + NSString *filename = [NSString stringWithFormat:@"public%@index.html", path]; + if ([[NSFileManager defaultManager] fileExistsAtPath:filename]) { + NSData *data = [NSData dataWithContentsOfFile:filename]; + [request setValue:[Nunja mimeTypeForFileWithName:filename] forResponseHeader:@"Content-Type"]; + [request setValue:@"max-age=3600" forResponseHeader:@"Cache-Control"]; + [request respondWithData:data]; + handled = YES; + } + } + if (!handled) { + NSString *filename = [NSString stringWithFormat:@"public%@prog_index.m3u8", path]; + if ([[NSFileManager defaultManager] fileExistsAtPath:filename]) { + NSData *data = [NSData dataWithContentsOfFile:filename]; + [request setValue:[Nunja mimeTypeForFileWithName:filename] forResponseHeader:@"Content-Type"]; + [request setValue:@"max-age=3600" forResponseHeader:@"Cache-Control"]; + [request respondWithData:data]; + handled = YES; + } + } + } + } + + if (!handled) { + // look for a file or directory that matches the path + NSString *filename = [NSString stringWithFormat:@"public%@", path]; + BOOL isDirectory = NO; + BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:filename isDirectory:&isDirectory]; + if (fileExists) { + if (isDirectory) { + unichar lastCharacter = [path characterAtIndex:[path length] - 1]; + if (lastCharacter != '/') { + // for a directory, redirect to the same path with '/' appended + [request setValue:[path stringByAppendingString:@"/"] forResponseHeader:@"Location"]; + [request respondWithCode:301 message:@"moved permanently" string:@"Moved Permanently"]; + handled = YES; + } + } else { + // for a file, send its contents + NSData *data = [NSData dataWithContentsOfFile:filename]; + [request setValue:[Nunja mimeTypeForFileWithName:filename] forResponseHeader:@"Content-Type"]; + [request setValue:@"max-age=3600" forResponseHeader:@"Cache-Control"]; + [request respondWithData:data]; + handled = YES; + } + } + } + + if (!handled) { // try appending .html to the path + NSString *filename = [NSString stringWithFormat:@"public%@.html", path]; + if ([[NSFileManager defaultManager] fileExistsAtPath:filename]) { + NSData *data = [NSData dataWithContentsOfFile:filename]; + [request setValue:@"text/html" forResponseHeader:@"Content-Type"]; + [request setValue:@"max-age=3600" forResponseHeader:@"Cache-Control"]; + [request respondWithData:data]; + handled = YES; + } + } + + if (!handled) { + if (defaultHandler) { + [defaultHandler handleRequest:request]; + } + else { + [request respondWithCode:404 + message:@"Not Found" + string:[NSString stringWithFormat:@"Not Found. You said: %@ %@", [request HTTPMethod], [request path]]]; + } + } +} + +- (void) nunjaDidFinishLaunching { + +} + +@end diff --git a/objc/NunjaRequest.h b/objc/NunjaRequest.h new file mode 100644 index 0000000..37cf684 --- /dev/null +++ b/objc/NunjaRequest.h @@ -0,0 +1,60 @@ + +#import + +#include +#include +#include +#include +#include + +void nunja_response_helper(struct evhttp_request *req, int code, NSString *message, NSData *data); +NSDictionary *nunja_request_headers_helper(struct evhttp_request *req); + +@class Nunja; + +@interface NunjaRequest : NSObject +{ + Nunja *nunja; + struct evhttp_request *req; + NSString *_uri; + NSString *_path; + NSDictionary *_parameters; + NSDictionary *_query; + NSDictionary *_bindings; + id _cookies; + int _responded; + int _responseCode; + NSString *_responseMessage; +} + +- (id) initWithNunja:(Nunja *)n request:(struct evhttp_request *)r; +- (Nunja *) nunja; +- (NSString *) uri; +- (NSString *) path; +- (NSDictionary *) parameters; +- (NSDictionary *) query; +- (id) bindings; +- (void) setBindings:(id) bindings; +- (NSData *) body; +- (NSString *) HTTPMethod; +- (NSString *) remoteHost; +- (int) remotePort; +- (NSDictionary *) requestHeaders; +- (NSDictionary *) responseHeaders; +- (int) setValue:(NSString *) value forResponseHeader:(NSString *) key; +- (NSString *) valueForResponseHeader:(NSString *) key; +- (int) removeResponseHeader:(NSString *) key; +- (void) clearResponseHeaders; +- (int) responseCode; +- (void) setResponseCode:(int) code message:(NSString *) message; +- (int) setValue:(NSString *) value forResponseHeader:(NSString *) key; +- (BOOL) respondWithString:(NSString *) string; +- (BOOL) respondWithData:(NSData *) data; +- (BOOL) respondWithCode:(int) code message:(NSString *) message string:(NSString *) string; +- (BOOL) respondWithCode:(int) code message:(NSString *) message data:(NSData *) data; +- (NSDictionary *) cookies; +- (void) setContentType:(NSString *)content_type; +- (int) redirectResponseToLocation:(NSString *) location; + + +@end diff --git a/objc/NunjaRequest.m b/objc/NunjaRequest.m new file mode 100644 index 0000000..c8fe662 --- /dev/null +++ b/objc/NunjaRequest.m @@ -0,0 +1,289 @@ +#import "NunjaRequest.h" +#import "Nunja.h" +#import + +#include +#include + +@interface NSString (Helpers) +- (NSDictionary *) urlQueryDictionary; +@end + +@implementation NunjaRequest + +- (id) initWithNunja:(Nunja *)n request:(struct evhttp_request *)r +{ + [super init]; + nunja = n; + req = r; + // get the URI + _uri = [[NSString alloc] initWithCString:evhttp_request_uri(req) encoding:NSUTF8StringEncoding]; + // scan for the path + int max = [_uri length]; + int base = 0; + int i = 0; + unichar c = 0; + while ((i < max) && ((c = [_uri characterAtIndex:i])) && (c != ';') && (c != '?')) + i++; + _path = [[_uri substringToIndex:i] retain]; + // if necessary, scan the object parameters + _parameters = nil; + if (c == ';') { + i = i + 1; + base = i; + while ((i < max) && ((c = [_uri characterAtIndex:i])) && (c != '?')) + i++; + NSString *parameterString = [_uri substringWithRange:NSMakeRange(base, i-base)]; + _parameters = [[parameterString urlQueryDictionary] retain]; + } + // if necessary, scan the query string + _query = nil; + if (c == '?') { + i = i + 1; + base = i; + while ((i < max) && ((c = [_uri characterAtIndex:i]))) + i++; + NSString *queryString = [_uri substringWithRange:NSMakeRange(base, i-base)]; + _query = [[queryString urlQueryDictionary] retain]; + } + // we haven't responded yet + _responded = NO; + // default response code is that everything is ok + _responseCode = HTTP_OK; + _responseMessage = @"OK"; + return self; +} + +- (void) dealloc +{ + [_uri release]; + [_path release]; + [_parameters release]; + [_query release]; + [_bindings release]; + [_cookies release]; + [super dealloc]; +} + +- (Nunja *) nunja {return nunja;} + +- (NSString *) uri +{ + return _uri; +} + +- (NSString *) path +{ + return _path; +} + +- (NSDictionary *) parameters +{ + return _parameters ? _parameters : [NSDictionary dictionary]; +} + +- (NSDictionary *) query +{ + return _query ? _query : [NSDictionary dictionary]; +} + +- (id) bindings +{ + return _bindings ? _bindings : [NSDictionary dictionary]; +} + +- (void) setBindings:(id) bindings +{ + [bindings retain]; + [_bindings release]; + _bindings = bindings; +} + +- (NSData *) body +{ + if (!req->input_buffer->buffer) + return nil; + else { + NSData *data = [NSData dataWithBytes:req->input_buffer->buffer length:req->input_buffer->off]; + return data; + } +} + +- (NSString *) HTTPMethod +{ + switch (req->type) { + case EVHTTP_REQ_GET: + return @"GET"; + case EVHTTP_REQ_POST: + return @"POST"; + case EVHTTP_REQ_HEAD: + return @"HEAD"; + default: + return @"UNKNOWN"; + } +} + +- (NSString *) remoteHost +{ + return [NSString stringWithCString:req->remote_host encoding:NSUTF8StringEncoding]; +} + +- (int) remotePort +{ + return req->remote_port; +} + +NSDictionary *nunja_request_headers_helper(struct evhttp_request *req) +{ + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + struct evkeyval *header; + TAILQ_FOREACH(header, req->input_headers, next) { + [dict setObject:[NSString stringWithCString:header->value encoding:NSUTF8StringEncoding] + forKey:[NSString stringWithCString:header->key encoding:NSUTF8StringEncoding]]; + } + return dict; +} + +- (NSDictionary *) requestHeaders +{ + return nunja_request_headers_helper(req); +} + +static NSDictionary *nunja_response_headers_helper(struct evhttp_request *req) +{ + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + struct evkeyval *header; + TAILQ_FOREACH(header, req->output_headers, next) { + NSString *value = [NSString stringWithCString:header->value encoding:NSUTF8StringEncoding]; + NSString *key = [NSString stringWithCString:header->key encoding:NSUTF8StringEncoding]; + if (value && key) { + [dict setObject:value forKey:key]; + } + } + return dict; +} + +- (NSDictionary *) responseHeaders +{ + return nunja_response_headers_helper(req); +} + +- (int) setValue:(NSString *) value forResponseHeader:(NSString *) key +{ + return evhttp_add_header(req->output_headers, [key cStringUsingEncoding:NSUTF8StringEncoding], [value cStringUsingEncoding:NSUTF8StringEncoding]); +} + +- (NSString *) valueForResponseHeader:(NSString *) key +{ + const char *value = evhttp_find_header(req->output_headers, [key cStringUsingEncoding:NSUTF8StringEncoding]); + return value ? [NSString stringWithCString:value encoding:NSUTF8StringEncoding] : nil; +} + +- (int) removeResponseHeader:(NSString *) key +{ + return evhttp_remove_header(req->output_headers, [key cStringUsingEncoding:NSUTF8StringEncoding]); +} + +- (void) clearResponseHeaders +{ + evhttp_clear_headers(req->output_headers); +} + +- (int) responseCode +{ + return _responseCode; +} + +- (void) setResponseCode:(int) code message:(NSString *) message +{ + _responseCode = code; + [message retain]; + [_responseMessage release]; + _responseMessage = message; +} + +void nunja_response_helper(struct evhttp_request *req, int code, NSString *message, NSData *data) +{ + if ([Nunja verbose]) { + NSLog(@"RESPONSE %d %@ %@", code, message, [nunja_response_headers_helper(req) description]); + } + struct evbuffer *buf = evbuffer_new(); + if (buf == NULL) { + NSLog(@"FATAL: failed to create response buffer"); + assert(0); + } + evbuffer_add(buf, [data bytes], [data length]); + evhttp_send_reply(req, code, [message cStringUsingEncoding:NSUTF8StringEncoding], buf); + evbuffer_free(buf); +} + +- (BOOL) respondWithString:(NSString *) string +{ + if (!_responded) { + nunja_response_helper(req, _responseCode, _responseMessage, [string dataUsingEncoding:NSUTF8StringEncoding]); + _responded = YES; + } + return YES; +} + +- (BOOL) respondWithData:(NSData *) data +{ + if (!_responded) { + nunja_response_helper(req, _responseCode, _responseMessage, data); + _responded = YES; + } + return YES; +} + +- (BOOL) respondWithCode:(int) code message:(NSString *) message string:(NSString *) string +{ + if (!_responded) { + nunja_response_helper(req, code, message, [string dataUsingEncoding:NSUTF8StringEncoding]); + _responded = YES; + } + return YES; +} + +- (BOOL) respondWithCode:(int) code message:(NSString *) message data:(NSData *) data +{ + if (!_responded) { + nunja_response_helper(req, code, message, data); + _responded = YES; + } + return YES; +} + +- (NSDictionary *) cookies +{ + static id cookie_pattern = nil; + if (!cookie_pattern) { + cookie_pattern = [[NSClassFromString(@"NuRegex") regexWithPattern:@"[ ]*([^=]*)=(.*)"] retain]; + } + if (!_cookies) { + _cookies = [[NSMutableDictionary alloc] init]; + NSString *cookieText = [[self requestHeaders] objectForKey:@"Cookie"]; + NSArray *parts = [cookieText componentsSeparatedByString:@";"]; + for (int i = 0; i < [parts count]; i++) { + NSString *cookieDescription = [parts objectAtIndex:i]; + id match = [cookie_pattern findInString:cookieDescription]; + if (match) { + [_cookies setObject:[match groupAtIndex:2] forKey:[match groupAtIndex:1]]; + } + } + } + return _cookies; +} + +- (void) setContentType:(NSString *)content_type +{ + [self setValue:content_type forResponseHeader:@"Content-Type"]; +} + +- (int) redirectResponseToLocation:(NSString *) location +{ + [self setValue:location forResponseHeader:@"Location"]; + [self respondWithCode:303 message:@"redirecting" string:@"redirecting"]; + return YES; +} + +@end diff --git a/objc/NunjaRequestHandler.h b/objc/NunjaRequestHandler.h new file mode 100644 index 0000000..fd95ec7 --- /dev/null +++ b/objc/NunjaRequestHandler.h @@ -0,0 +1,20 @@ + +#import "Nunja.h" +#import "NunjaRequest.h" + +@interface NunjaRequestHandler : NSObject { + NSString *httpMethod; + NSString *path; + id block; // A Nu or C block to be invoked to handle the request. + + NSMutableArray *parts; // internal, used to expand pattern for request routing +} + ++ (NunjaRequestHandler *) handlerWithHTTPMethod:(id)httpMethod path:(id)path block:(id)block; + +- (NSString *) httpMethod; +- (NSString *) path; +- (NSMutableArray *) parts; + +- (BOOL) handleRequest:(NunjaRequest *)request; +@end \ No newline at end of file diff --git a/objc/NunjaRequestHandler.m b/objc/NunjaRequestHandler.m new file mode 100644 index 0000000..0c44681 --- /dev/null +++ b/objc/NunjaRequestHandler.m @@ -0,0 +1,91 @@ +#import "NunjaRequestHandler.h" +#import "NunjaRequest.h" +#import + +@implementation NunjaRequestHandler + +- (NSString *) path { + return path; +} + +- (NSString *) httpMethod { + return httpMethod; +} + +- (NSMutableArray *) parts { + return self->parts; +} + ++ (NunjaRequestHandler *) handlerWithHTTPMethod:(id)httpMethod path:(id)path block:(id)block +{ + NunjaRequestHandler *handler = [[[NunjaRequestHandler alloc] init] autorelease]; + handler->httpMethod = [httpMethod retain]; + handler->path = [path retain]; + handler->parts = [[[NSString stringWithFormat:@"%@%@", httpMethod, path] componentsSeparatedByString:@"/"] retain]; + handler->block = [block retain]; + return handler; +} + +// Handle a request. Used internally. +- (BOOL) handleRequest:(NunjaRequest *)request +{ + if ([Nunja verbose]) { + NSLog(@"handling request %@", [request uri]); + NSLog(@"request from host %@ port %d", [request remoteHost], [request remotePort]); + } + + id args = [[[NSClassFromString(@"NuCell") alloc] init] autorelease]; + [args setCar:request]; + + id body = nil; + if ([block isKindOfClass:NSClassFromString(@"NuBlock")]) { + // evaluate block with request as the single argument + body = [block evalWithArguments:args context:[NSMutableDictionary dictionary]]; + } +#ifdef DARWIN + else { + // evaluate block as a C block + body = ((id(^)(id)) block)(request); + } +#endif + + if ([Nunja verbose]) { + NSLog(@"evaluated with status %d", [request responseCode]); + } + + static id text_html_pattern = nil; + if (!text_html_pattern) { + text_html_pattern = [[NSClassFromString(@"NuRegex") regexWithPattern:@"^text/html.*$"] retain]; + } + + id content_type; + + if (!body || (body == [NSNull null])) { + // return without responding, this means the handler has rejected the URL + return NO; + } + else if ([body isKindOfClass:[NSData class]]) { + // return data objects as-is + // we should set the content type if it isn't set + [request respondWithData:body]; + return YES; // just non-nil + } + else if (![body isKindOfClass:[NSString class]]) { + // return other non-strings as their stringValues + [request respondWithString:[body stringValue]]; + return YES; + } + else if ((content_type = [request valueForResponseHeader:@"Content-Type"]) && ![text_html_pattern findInString:content_type]) { + // if a content type is set and it isn't text/html, return string as-is + [request respondWithString:body]; + return YES; + } + else { + // return string as html + [request setContentType:@"text/html; charset=UTF-8"]; + [request respondWithString:body]; + return YES; + } +} + +@end diff --git a/objc/NunjaRequestRouter.h b/objc/NunjaRequestRouter.h new file mode 100644 index 0000000..768edff --- /dev/null +++ b/objc/NunjaRequestRouter.h @@ -0,0 +1,18 @@ +#import + +@class NunjaRequest; +@class NunjaRequestHandler; + +@interface NunjaRequestRouter : NSObject { + NSMutableDictionary *contents; + NSString *token; + NunjaRequestHandler *handler; +} + ++ (NunjaRequestRouter *) routerWithToken:(id) token; +- (NSString *) token; +- (void) dump:(int) level; +- (id) routeRequest:(NunjaRequest *) request parts:(NSArray *) parts level:(int) level; +- (void) insertHandler:(NunjaRequestHandler *) handler level:(int) level; +@end + diff --git a/objc/NunjaRequestRouter.m b/objc/NunjaRequestRouter.m new file mode 100644 index 0000000..dbb375a --- /dev/null +++ b/objc/NunjaRequestRouter.m @@ -0,0 +1,85 @@ + +#import "NunjaRequestHandler.h" +#import "NunjaRequestRouter.h" +#import "NunjaRequest.h" + +NSString *spaces(int n) { + NSMutableString *result = [NSMutableString string]; + for (int i = 0; i < n; i++) { + [result appendString:@" "]; + } + return result; +} + + +@implementation NunjaRequestRouter + ++ (NunjaRequestRouter *) routerWithToken:(NSString *) token +{ + NunjaRequestRouter *router = [[[self alloc] init] autorelease]; + router->contents = [[NSMutableDictionary alloc] init]; + router->token = [token retain]; + return router; +} + +- (id) token { + return token; +} + +- (void) dump:(int) level +{ + NSLog(@"%@%@\t%@", spaces(level), self->token, self->handler); + id keys = [self->contents allKeys]; + for (int i = 0; i < [keys count]; i++) { + id key = [keys objectAtIndex:i]; + id value = [self->contents objectForKey:key]; + [value dump:(level+1)]; + } +} + +- (id) routeRequest:(NunjaRequest *) request parts:(NSArray *) parts level:(int) level +{ + if (level == [parts count]) { + return self->handler; + } + else { + id key = [parts objectAtIndex:level]; + id response; + id child = [self->contents objectForKey:key]; + if (child && (response = [child routeRequest:request parts:parts level:(level+1)])) { + // if the response is non-null, we have a match + return response; + } + else if (child = [self->contents objectForKey:@":"]) { + [[request bindings] setObject:key forKey:[[child token] substringToIndex:([[child token] length]-1)]]; + return [child routeRequest:request parts:parts level:(level + 1)]; + } + else { + return nil; + } + } +} + +- (void) insertHandler:(NunjaRequestHandler *) h level:(int) level +{ + if (level == [[h parts] count]) { + self->handler = [h retain]; + } + else { + id key = [[h parts] objectAtIndex:level]; + BOOL key_is_wildcard = ([key length] > 0) && ([key characterAtIndex:([key length] - 1)] == ':'); + id child = [self->contents objectForKey:(key_is_wildcard ? @":" : key)]; + if (!child) { + child = [NunjaRequestRouter routerWithToken:key]; + } + if (([key length] > 0) && (([key characterAtIndex:0] == ':') || ([key characterAtIndex:([key length] -1)] == ':'))) { + [self->contents setObject:child forKey:@":"]; + } + else { + [self->contents setObject:child forKey:key]; + } + [child insertHandler:h level:level+1]; + } +} + +@end diff --git a/objc/util.m b/objc/Nunja_daemonize.m similarity index 85% rename from objc/util.m rename to objc/Nunja_daemonize.m index 2d8df5d..2b29e96 100644 --- a/objc/util.m +++ b/objc/Nunja_daemonize.m @@ -1,5 +1,5 @@ /*! -@file util.m +@file Nunja_daemonize.m @discussion General utilities for the Nunja web server. @copyright Copyright (c) 2008 Neon Design Technology, Inc. @@ -27,9 +27,13 @@ #include #import +#import "Nunja.h" // http://www.netzmafia.de/skripten/unix/linux-daemon-howto.html -void daemonize() + +@implementation Nunja (daemonize) + ++ (void) daemonize { pid_t pid, sid; @@ -68,16 +72,4 @@ void daemonize() close(STDERR_FILENO); } -@implementation NSFileManager (Nunja) - -- (BOOL) directoryExistsAtPath:(NSString *) path -{ - BOOL isDirectory = NO; - BOOL fileExists = [self fileExistsAtPath:path isDirectory:&isDirectory]; - if (!fileExists) - return NO; - else - return isDirectory; -} - @end diff --git a/objc/markup.m b/objc/markup.m deleted file mode 100644 index e1e0d14..0000000 --- a/objc/markup.m +++ /dev/null @@ -1,96 +0,0 @@ -#import - -@interface NuOperator : NSObject -{ -} - -- (id) evalWithArguments:(id) cdr context:(NSMutableDictionary *) context; -- (id) callWithArguments:(id) cdr context:(NSMutableDictionary *) context; -@end - -@interface NunjaMarkupOperator : NuOperator -{ - NSString *tag; - NSString *prefix; -} - -- (id) initWithTag:(NSString *) tag; -- (id) initWithTag:(NSString *) tag prefix:(NSString *) prefix; -@end - -@implementation NunjaMarkupOperator - -+ (id) operatorWithTag:(NSString *) _tag -{ - return [[[self alloc] initWithTag:_tag] autorelease]; -} - -+ (id) operatorWithTag:(NSString *) _tag prefix:(NSString *) _prefix -{ - return [[[self alloc] initWithTag:_tag prefix:_prefix] autorelease]; -} - -- (id) initWithTag:(NSString *) _tag -{ - return [self initWithTag:_tag prefix:nil]; -} - -- (id) initWithTag:(NSString *) _tag prefix:(NSString *) _prefix -{ - self = [super init]; - tag = _tag ? [_tag retain] : @"undefined-element"; - prefix = _prefix ? [_prefix retain] : @""; - return self; -} - -- (id) callWithArguments:(id)cdr context:(NSMutableDictionary *)context -{ - NSMutableString *body = [NSMutableString string]; - NSMutableString *attributes = [NSMutableString string]; - - static id NuSymbol = nil; - if (!NuSymbol) { - NuSymbol = NSClassFromString(@"NuSymbol"); - } - - id cursor = cdr; - while (cursor && (cursor != [NSNull null])) { - id item = [cursor car]; - if ([item isKindOfClass:[NuSymbol class]] && [item isLabel]) { - cursor = [cursor cdr]; - if (cursor && (cursor != [NSNull null])) { - id value = [[cursor car] evalWithContext:context]; - [attributes appendFormat:@" %@=\"%@\"", [item labelName], [value stringValue]]; - } - } - else { - id evaluatedItem = [item evalWithContext:context]; - if ([evaluatedItem isKindOfClass:[NSString class]]) { - [body appendString:evaluatedItem]; - } - else if ([evaluatedItem isKindOfClass:[NSArray class]]) { - int max = [evaluatedItem count]; - for (int i = 0; i < max; i++) { - [body appendString:[evaluatedItem objectAtIndex:i]]; - } - } - else if (evaluatedItem == [NSNull null]) { - // do nothing - } - else { - [body appendString:[evaluatedItem stringValue]]; - } - } - if (cursor && (cursor != [NSNull null])) - cursor = [cursor cdr]; - } - - if ([body length]) { - return [NSString stringWithFormat:@"%@<%@%@>%@", prefix, tag, attributes, body, tag]; - } - else { - return [NSString stringWithFormat:@"%@<%@%@/>", prefix, tag, attributes]; - } -} - -@end diff --git a/objc/nunja.h b/objc/nunja.h deleted file mode 100644 index 941f453..0000000 --- a/objc/nunja.h +++ /dev/null @@ -1,6 +0,0 @@ - - -#import - -@interface SuperNunja : NSObject {} -@end \ No newline at end of file diff --git a/objc/nunja.m b/objc/nunja.m deleted file mode 100644 index 9dc8c09..0000000 --- a/objc/nunja.m +++ /dev/null @@ -1,548 +0,0 @@ -/*! -@file nunja.m -@discussion Objective-C components of the Nunja web server. -@copyright Copyright (c) 2008 Neon Design Technology, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -#include -#include -#include -#include -#include -#define HTTP_SEEOTHER 303 -#define HTTP_DENIED 403 - -#include -#include - -#import -#import -#import "nunja.h" - -void NunjaInit() -{ - static initialized = 0; - if (!initialized) { - initialized = 1; - [Nu loadNuFile:@"nunja" fromBundleWithIdentifier:@"nu.programming.nunja" withContext:nil]; - } -} - -@class Nunja; - -@implementation SuperNunja -@end - -static BOOL verbose_nunja = NO; -static BOOL local_nunja = NO; -static BOOL autotags = YES; - -@interface NunjaRequest : NSObject -{ - Nunja *nunja; - struct evhttp_request *req; - NSString *_uri; - NSString *_path; - NSDictionary *_parameters; - NSDictionary *_query; - id _match; - NSDictionary *_bindings; - id _cookies; - int _responded; - int _responseCode; - NSString *_responseMessage; -} - -@end - -@implementation NunjaRequest - -- (id) initWithNunja:(Nunja *)n request:(struct evhttp_request *)r -{ - [super init]; - nunja = n; - req = r; - // get the URI - _uri = [[NSString alloc] initWithCString:evhttp_request_uri(req) encoding:NSUTF8StringEncoding]; - // scan for the path - int max = [_uri length]; - int base = 0; - int i = 0; - unichar c; - while ((i < max) && ((c = [_uri characterAtIndex:i])) && (c != ';') && (c != '?')) - i++; - _path = [[_uri substringToIndex:i] retain]; - // if necessary, scan the object parameters - _parameters = nil; - if (c == ';') { - i = i + 1; - base = i; - while ((i < max) && ((c = [_uri characterAtIndex:i])) && (c != '?')) - i++; - NSString *parameterString = [_uri substringWithRange:NSMakeRange(base, i-base)]; - _parameters = [[parameterString urlQueryDictionary] retain]; - } - // if necessary, scan the query string - _query = nil; - if (c == '?') { - i = i + 1; - base = i; - while ((i < max) && ((c = [_uri characterAtIndex:i]))) - i++; - NSString *queryString = [_uri substringWithRange:NSMakeRange(base, i-base)]; - _query = [[queryString urlQueryDictionary] retain]; - } - // we haven't responded yet - _responded = NO; - // default response code is that everything is ok - _responseCode = HTTP_OK; - _responseMessage = @"OK"; - return self; -} - -- (void) dealloc -{ - [_uri release]; - [_path release]; - [_parameters release]; - [_query release]; - [_match release]; - [_bindings release]; - [_cookies release]; - [super dealloc]; -} - -- (Nunja *) nunja {return nunja;} - -- (NSString *) uri -{ - return _uri; -} - -- (NSString *) path -{ - return _path; -} - -- (NSDictionary *) parameters -{ - return _parameters; -} - -- (NSDictionary *) query -{ - return _query; -} - -- (id) match -{ - return _match; -} - -- (void) setMatch:(id) match -{ - [match retain]; - [_match release]; - _match = match; -} - -- (id) bindings -{ - return _bindings; -} - -- (void) setBindings:(id) bindings -{ - [bindings retain]; - [_bindings release]; - _bindings = bindings; -} - -- (NSData *) body -{ - if (!req->input_buffer->buffer) - return nil; - else { - NSData *data = [NSData dataWithBytes:req->input_buffer->buffer length:req->input_buffer->off]; - } -} - -- (NSString *) command -{ - switch (req->type) { - case EVHTTP_REQ_GET: - return @"GET"; - case EVHTTP_REQ_POST: - return @"POST"; - case EVHTTP_REQ_HEAD: - return @"HEAD"; - default: - return @"UNKNOWN"; - } -} - -- (NSString *) method -{ - return [self command]; -} - -- (NSString *) remoteHost -{ - return [NSString stringWithCString:req->remote_host encoding:NSUTF8StringEncoding]; -} - -- (int) remotePort -{ - return req->remote_port; -} - -static NSDictionary *nunja_request_headers_helper(struct evhttp_request *req) -{ - NSMutableDictionary *dict = [NSMutableDictionary dictionary]; - struct evkeyval *header; - TAILQ_FOREACH(header, req->input_headers, next) { - [dict setObject:[NSString stringWithCString:header->value encoding:NSUTF8StringEncoding] - forKey:[NSString stringWithCString:header->key encoding:NSUTF8StringEncoding]]; - } - return dict; -} - -- (NSDictionary *) requestHeaders -{ - return nunja_request_headers_helper(req); -} - -static NSDictionary *nunja_response_headers_helper(struct evhttp_request *req) -{ - NSMutableDictionary *dict = [NSMutableDictionary dictionary]; - struct evkeyval *header; - TAILQ_FOREACH(header, req->output_headers, next) { - [dict setObject:[NSString stringWithCString:header->value encoding:NSUTF8StringEncoding] - forKey:[NSString stringWithCString:header->key encoding:NSUTF8StringEncoding]]; - } - return dict; -} - -- (NSDictionary *) responseHeaders -{ - return nunja_response_headers_helper(req); -} - -- (int) setValue:(const char *) value forResponseHeader:(const char *) key -{ - return evhttp_add_header(req->output_headers, key, value); -} - -- (NSString *) valueForResponseHeader:(const char *) key -{ - const char *value = evhttp_find_header(req->output_headers, key); - return value ? [NSString stringWithCString:value encoding:NSUTF8StringEncoding] : nil; -} - -- (int) removeResponseHeader:(const char *) key -{ - return evhttp_remove_header(req->output_headers, key); -} - -- (void) clearResponseHeaders -{ - evhttp_clear_headers(req->output_headers); -} - -- (int) responseCode { - return _responseCode; -} - -- (void) setResponseCode:(int) code message:(NSString *) message { - _responseCode = code; - [message retain]; - [_responseMessage release]; - _responseMessage = message; -} - -static void nunja_response_helper(struct evhttp_request *req, int code, NSString *message, NSData *data) -{ - if (verbose_nunja) { - NSLog(@"RESPONSE %d %@ %@", code, message, [nunja_response_headers_helper(req) description]); - } - struct evbuffer *buf = evbuffer_new(); - if (buf == NULL) err(1, "failed to create response buffer"); - evbuffer_add(buf, [data bytes], [data length]); - evhttp_send_reply(req, code, [message cStringUsingEncoding:NSUTF8StringEncoding], buf); - evbuffer_free(buf); -} - -- (BOOL) respondWithString:(NSString *) string -{ - if (!_responded) { - nunja_response_helper(req, _responseCode, _responseMessage, [string dataUsingEncoding:NSUTF8StringEncoding]); - _responded = YES; - } - return YES; -} - -- (BOOL) respondWithData:(NSData *) data -{ - if (!_responded) { - nunja_response_helper(req, _responseCode, _responseMessage, data); - _responded = YES; - } - return YES; -} - -- (BOOL) respondWithCode:(int) code message:(NSString *) message string:(NSString *) string -{ - if (!_responded) { - nunja_response_helper(req, code, message, [string dataUsingEncoding:NSUTF8StringEncoding]); - _responded = YES; - } - return YES; -} - -- (BOOL) respondWithCode:(int) code message:(NSString *) message data:(NSData *) data -{ - if (!_responded) { - nunja_response_helper(req, code, message, data); - _responded = YES; - } - return YES; -} - -@end - -@protocol NunjaController -- (void) handleRequest:(NunjaRequest *)request; -@end - -@interface Nunja : SuperNunja -{ - struct event_base *event_base; - struct evhttp *httpd; - id controller; -} - -- (id) controller; -@end - -@implementation Nunja - -+ (void) setVerbose:(BOOL) v -{ - verbose_nunja = v; -} - -+ (BOOL) verbose {return verbose_nunja;} - -+ (void) setLocalOnly:(BOOL) l -{ - local_nunja = l; -} - -+ (BOOL) localOnly {return local_nunja;} - -+ (void) load -{ - NunjaInit(); -} - -static void nunja_request_handler(struct evhttp_request *req, void *nunja_pointer) -{ - Nunja *nunja = (Nunja *) nunja_pointer; - id controller = [nunja controller]; - if (controller) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NunjaRequest *request = [[NunjaRequest alloc] initWithNunja:nunja request:req]; - [controller handleRequest:request]; - [request release]; - [pool release]; - } - else { - nunja_response_helper(req, HTTP_OK, @"OK", - [[NSString stringWithFormat:@"Please set the Nunja server controller.
If you are running nunjad, use the '-s' option to specify a site.
\nRequest: %s\n", - evhttp_request_uri(req)] - dataUsingEncoding:NSUTF8StringEncoding]); - } -} - -- (id) init -{ - [super init]; - event_base = event_init(); - evdns_init(); - httpd = evhttp_new(event_base); - evhttp_set_gencb(httpd, nunja_request_handler, self); - controller = nil; - return self; -} - -- (int) bindToAddress:(const char *) address port:(int) port -{ - return evhttp_bind_socket(httpd, address, port); -} - -- (void) run -{ - event_base_dispatch(event_base); -} - -- (void) dealloc -{ - evhttp_free(httpd); - [super dealloc]; -} - -- (id) controller -{ - return controller; -} - -- (void) setController:(id) d -{ - [d retain]; - [controller release]; - controller = d; -} - -@class NuBlock; -@class NuCell; - -static void nunja_dns_gethostbyname_cb(int result, char type, int count, int ttl, void *addresses, void *arg) -{ - id address = nil; - if (result == DNS_ERR_TIMEOUT) { - fprintf(stdout, "[Timed out] "); - } - else if (result != DNS_ERR_NONE) { - fprintf(stdout, "[Error code %d] ", result); - } - else { - fprintf(stdout, "type: %d, count: %d, ttl: %d\n", type, count, ttl); - switch (type) { - case DNS_IPv4_A: - { - struct in_addr *in_addrs = addresses; - if (ttl < 0) { - // invalid resolution - } - else if (count == 0) { - // no addresses - } - else { - address = [NSString stringWithFormat:@"%s", inet_ntoa(in_addrs[0])]; - } - break; - } - case DNS_PTR: - /* may get at most one PTR */ - // this needs review. TB. - if (count == 1) - fprintf(stdout, "addresses: %s ", *(char **)addresses); - break; - default: - break; - } - } - NuBlock *block = (NuBlock *) arg; - NuCell *args = [[NuCell alloc] init]; - [args setCar:address]; - [block evalWithArguments:args context:nil]; - [block release]; - [args release]; -} - -- (void) resolveDomainName:(NSString *) name andDo:(NuBlock *) block -{ - [block retain]; - evdns_resolve_ipv4([name cStringUsingEncoding:NSUTF8StringEncoding], 0, nunja_dns_gethostbyname_cb, block); -} - -void nunja_http_request_done(struct evhttp_request *req, void *arg) -{ - fprintf(stdout, "received %d bytes\n", (int) arg); - NSData *data = nil; - if (req->response_code != HTTP_OK) { - if (req->response_code == HTTP_SEEOTHER) { - fprintf(stdout, "REDIRECTING\n"); - NSDictionary *headers = nunja_request_headers_helper(req); - return; // this is not handled yet. - } - fprintf(stdout, "FAILED to get OK (response = %d)\n", req->response_code); - } - else if (evhttp_find_header(req->input_headers, "Content-Type") == NULL) { - fprintf(stdout, "FAILED to find Content-Type\n"); - } - else { - data = [NSData dataWithBytes:EVBUFFER_DATA(req->input_buffer) length:EVBUFFER_LENGTH(req->input_buffer)]; - } - NuBlock *block = (NuBlock *) arg; - NuCell *args = [[NuCell alloc] init]; - [args setCar:data]; - [block evalWithArguments:args context:nil]; - [block release]; - [args release]; - fprintf(stdout, "end of callback\n"); - // leaking... - //evhttp_connection_free(req->evcon); -} - -- (void) getResourceFromHost:(NSString *) host address:(NSString *) address port:(int)port path:(NSString *)path andDo:(NuBlock *) block -{ - [block retain]; - // make the connection - struct evhttp_connection *evcon = evhttp_connection_new([address cStringUsingEncoding:NSUTF8StringEncoding], port); - if (evcon == NULL) { - fprintf(stdout, "FAILED to connect\n"); - NuCell *args = [[NuCell alloc] init]; - [block evalWithArguments:args context:nil]; - [block release]; - [args release]; - return; - } - // make the request - struct evhttp_request *req = evhttp_request_new(nunja_http_request_done, block); - evhttp_add_header(req->output_headers, "Host", [host cStringUsingEncoding:NSUTF8StringEncoding]); - // give ownership of the request to the connection - if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, [path cStringUsingEncoding:NSUTF8StringEncoding]) == -1) { - fprintf(stdout, "FAILED to make the request \n"); - } -} - -- (void) postDataToHost:(NSString *) host address:(NSString *) address port:(int)port path:(NSString *)path data:(NSData *) data andDo:(NuBlock *) block -{ - [block retain]; - // make the connection - struct evhttp_connection *evcon = evhttp_connection_new([address cStringUsingEncoding:NSUTF8StringEncoding], port); - if (evcon == NULL) { - fprintf(stdout, "FAILED to connect\n"); - NuCell *args = [[NuCell alloc] init]; - [block evalWithArguments:args context:nil]; - [block release]; - [args release]; - return; - } - // make the request - struct evhttp_request *req = evhttp_request_new(nunja_http_request_done, block); - evhttp_add_header(req->output_headers, "Host", [host cStringUsingEncoding:NSUTF8StringEncoding]); - evhttp_add_header(req->output_headers, "Content-Length", [[NSString stringWithFormat:@"%d", [data length]] cStringUsingEncoding:NSUTF8StringEncoding]); - evhttp_add_header(req->output_headers, "Content-Type", "application/x-www-form-urlencoded"); - evbuffer_add(req->output_buffer, [data bytes], [data length]); - - // give ownership of the request to the connection - if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, [path cStringUsingEncoding:NSUTF8StringEncoding]) == -1) { - fprintf(stdout, "FAILED to make the request \n"); - } -} - -@end diff --git a/objc/passwd.m b/objc/passwd.m deleted file mode 100644 index 744da6a..0000000 --- a/objc/passwd.m +++ /dev/null @@ -1,182 +0,0 @@ -#import "nunja.h" - -char *md5_crypt(const char *pw, const char *salt); - -@implementation SuperNunja (SaltedPasswords) - -+ (NSString *) saltedPassword:(NSString *) password withSalt:(NSString *) salt -{ - char *passwordString = [password cStringUsingEncoding:NSUTF8StringEncoding]; - char *saltString = [salt cStringUsingEncoding:NSUTF8StringEncoding]; - - size_t pw_maxlen = 256; - - /* truncate password if necessary */ - if ((strlen(passwordString) > pw_maxlen)) { - passwordString[pw_maxlen] = 0; - } - - /* now compute password hash */ - char *hash = md5_crypt(passwordString, saltString); - - return [[NSString alloc] initWithCString:hash encoding:NSUTF8StringEncoding]; -} - -@end - -////// The remainder of this file is covered by the following license: - -/* - * ---------------------------------------------------------------------------- - * "THE BEER-WARE LICENSE" (Revision 42): - * wrote this file. As long as you retain this notice you - * can do whatever you want with this stuff. If we meet some day, and you think - * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp - * ---------------------------------------------------------------------------- - */ - -/* - * Ported from FreeBSD to Linux, only minimal changes. --marekm - */ - -/* - * Adapted from shadow-19990607 by Tudor Bosman, tudorb@jm.nu - */ - -#pragma ident "%Z%%M% %I% %E% SMI" - -#include - -static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */ - "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - -static char *magic = "$1$"; /* - * This string is magic for - * this algorithm. Having - * it this way, we can get - * get better later on - */ - -static void -to64(char *s, unsigned long v, int n) -{ - while (--n >= 0) { - *s++ = itoa64[v&0x3f]; - v >>= 6; - } -} - -int -is_md5_salt(const char *salt) -{ - return (!strncmp(salt, magic, strlen(magic))); -} - -/* - * UNIX password - * - * Use MD5 for what it is best at... - */ - -char * -md5_crypt(const char *pw, const char *salt) -{ - static char passwd[120], *p; - static const char *sp,*ep; - unsigned char final[16]; - int sl,pl,i,j; - MD5_CTX ctx,ctx1; - unsigned long l; - - /* Refine the Salt first */ - sp = salt; - - /* If it starts with the magic string, then skip that */ - if(!strncmp(sp,magic,strlen(magic))) - sp += strlen(magic); - - /* It stops at the first '$', max 8 chars */ - for(ep=sp;*ep && *ep != '$' && ep < (sp+8);ep++) - continue; - - /* get the length of the true salt */ - sl = ep - sp; - - MD5_Init(&ctx); - - /* The password first, since that is what is most unknown */ - MD5_Update(&ctx,pw,strlen(pw)); - - /* Then our magic string */ - MD5_Update(&ctx,magic,strlen(magic)); - - /* Then the raw salt */ - MD5_Update(&ctx,sp,sl); - - /* Then just as many characters of the MD5(pw,salt,pw) */ - MD5_Init(&ctx1); - MD5_Update(&ctx1,pw,strlen(pw)); - MD5_Update(&ctx1,sp,sl); - MD5_Update(&ctx1,pw,strlen(pw)); - MD5_Final(final,&ctx1); - for(pl = strlen(pw); pl > 0; pl -= 16) - MD5_Update(&ctx,final,pl>16 ? 16 : pl); - - /* Don't leave anything around in vm they could use. */ - memset(final,0,sizeof final); - - /* Then something really weird... */ - for (j=0,i = strlen(pw); i ; i >>= 1) - if(i&1) - MD5_Update(&ctx, final+j, 1); - else - MD5_Update(&ctx, pw+j, 1); - - /* Now make the output string */ - strcpy(passwd,magic); - strncat(passwd,sp,sl); - strcat(passwd,"$"); - - MD5_Final(final,&ctx); - - /* - * and now, just to make sure things don't run too fast - * On a 60 Mhz Pentium this takes 34 msec, so you would - * need 30 seconds to build a 1000 entry dictionary... - */ - for(i=0;i<1000;i++) { - MD5_Init(&ctx1); - if(i & 1) - MD5_Update(&ctx1,pw,strlen(pw)); - else - MD5_Update(&ctx1,final,16); - - if(i % 3) - MD5_Update(&ctx1,sp,sl); - - if(i % 7) - MD5_Update(&ctx1,pw,strlen(pw)); - - if(i & 1) - MD5_Update(&ctx1,final,16); - else - MD5_Update(&ctx1,pw,strlen(pw)); - MD5_Final(final,&ctx1); - } - - p = passwd + strlen(passwd); - - l = (final[ 0]<<16) | (final[ 6]<<8) | final[12]; to64(p,l,4); p += 4; - l = (final[ 1]<<16) | (final[ 7]<<8) | final[13]; to64(p,l,4); p += 4; - l = (final[ 2]<<16) | (final[ 8]<<8) | final[14]; to64(p,l,4); p += 4; - l = (final[ 3]<<16) | (final[ 9]<<8) | final[15]; to64(p,l,4); p += 4; - l = (final[ 4]<<16) | (final[10]<<8) | final[ 5]; to64(p,l,4); p += 4; - l = final[11] ; to64(p,l,2); p += 2; - *p = '\0'; - - /* Don't leave anything around in vm they could use. */ - memset(final,0,sizeof final); - - return passwd; -} - diff --git a/sample/site.nu b/sample/site.nu index f2caf7b..6cddc30 100644 --- a/sample/site.nu +++ b/sample/site.nu @@ -15,6 +15,47 @@ ;; limitations under the License. (load "NuHTTPHelpers") +(load "Nu:template") + +;; import some useful C functions +(global random (NuBridgedFunction functionWithName:"random" signature:"l")) +(global srandom (NuBridgedFunction functionWithName:"srandom" signature:"vI")) + +(class NunjaRequest + (- post is ((self body) urlQueryDictionary))) + +;; @class NunjaCookie +;; @discussion A class for managing user-identifying cookies. +(class NunjaCookie is NSObject + (ivars) + (ivar-accessors) + + ;; Generate a random identifier for use in a cookie. + (+ (id) randomIdentifier is + "#{((random) stringValue)}#{((random) stringValue)}#{((random) stringValue)}#{((random) stringValue)}") + + ;; Construct a cookie for a specified user. + (+ (id) cookieForUser:(id) user is + ((self alloc) initWithUser:user + value:(self randomIdentifier) + expiration:(NSDate dateWithTimeIntervalSinceNow:3600))) + + ;; Initialize a cookie for a specified user. + (- (id) initWithUser:(id) user value:(id) value expiration:(id) expiration is + (super init) + (set @name "session") + (set @user user) + (set @value value) + (set @expiration expiration) + (set @stringValue nil) + self) + + ;; Get a string description of a cookie. + (- (id) description is + "cookie=#{@name} value=#{@value} user=#{@user} expiration=#{(@expiration rfc822)}") + + ;; Get a string value for a cookie suitable for inclusion in a response header. + (- (id) stringValue is "#{@name}=#{@value}; Expires:#{(@expiration rfc1123)}; Path=/")) ;; global variables (set sessionCookies (dict)) @@ -88,7 +129,7 @@ HTML)) forKey:"BODY") (set post (REQUEST post)) (if (eq (post "response") "Cancel") (then - (Nunja redirectResponse:REQUEST toLocation:"/")) + (REQUEST redirectResponseToLocation:"/")) (else (set username (post "username")) (set password (post "password")) @@ -97,7 +138,7 @@ HTML)) forKey:"BODY") (set sessionCookie (NunjaCookie cookieForUser:username)) (sessionCookies setObject:sessionCookie forKey:(sessionCookie value)) (REQUEST setValue:(sessionCookie stringValue) forResponseHeader:"Set-Cookie") - (Nunja redirectResponse:REQUEST toLocation:"/")) + (REQUEST redirectResponseToLocation:"/")) (else (RESPONSE setValue:"Please try again" forKey:"TITLE") (RESPONSE setValue:(eval (NuTemplate codeForString:<<-HTML @@ -115,7 +156,7 @@ HTML)) forKey:"BODY") (get "/logout" (set sessionCookieName ((REQUEST cookies) "session")) (if sessionCookieName (sessionCookies removeObjectForKey:sessionCookieName)) - (Nunja redirectResponse:REQUEST toLocation:"/")) + (REQUEST redirectResponseToLocation:"/")) ;; add-a-friend page. (get "/addfriend" @@ -139,13 +180,13 @@ HTML)) forKey:"BODY") (set post (REQUEST post)) (if (eq (post "response") "Submit") (friends << (dict name:(post "name") email:(post "email")))) - (Nunja redirectResponse:REQUEST toLocation:"/")) + (REQUEST redirectResponseToLocation:"/")) ;; delete-a-friend with a GET. Strictly, this should be a post, but we use a get to show how it would be done. (get "/delete" (set post (REQUEST query)) (set friends (friends select:(do (friend) (!= (friend "name") (post "name"))))) - (Nunja redirectResponse:REQUEST toLocation:"/")) + (REQUEST redirectResponseToLocation:"/")) (get "/about" (set RESPONSE (dict)) @@ -186,9 +227,9 @@ END forKey:"BODY") (eval page-layout)) ;; large file download -(get (regex -"/data(.*)") +(get "/data/size:" (REQUEST setValue:"application/octet-stream" forResponseHeader:"Content-Type") - (set size ((REQUEST match) groupAtIndex:1)) + (set size ((REQUEST bindings) size:)) (set megabytes (if (eq size "") then 1 else (size doubleValue))) @@ -196,8 +237,8 @@ END forKey:"BODY") ;; perform a dns lookup ;; ex: /dns/programming.nu -(get (regex -"/dns/(.*)") - (set hostname ((REQUEST match) groupAtIndex:1)) +(get "/dns/hostname:" + (set hostname ((REQUEST bindings) hostname:)) ((REQUEST nunja) resolveDomainName:hostname andDo: (do (address) (if address @@ -206,23 +247,25 @@ END forKey:"BODY") (REQUEST respondWithString:"unable to resolve #{hostname}")))) nil) ;; return nil to leave the connection open -;; request and return a resource from a specified host -;; ex: /proxy/programming.nu/about -(get (regex -"/proxy/([^\/]+)/(.*)") - (set host ((REQUEST match) groupAtIndex:1)) - (set path (+ "/" ((REQUEST match) groupAtIndex:2))) - ((REQUEST nunja) resolveDomainName:host andDo: - (do (address) - (if address - (then ((REQUEST nunja) getResourceFromHost:host address:address port:80 path:path andDo: - (do (data) - (if data - (then (REQUEST respondWithData:data)) - (else (REQUEST respondWithString:"unable to load #{path}")))))) - (else (REQUEST respondWithString:"unable to resolve host #{host}"))))) - nil) ;; return nil to leave the connection open +(if NO + ;; request and return a resource from a specified host + ;; ex: /proxy/programming.nu/about + (get (regex -"/proxy/([^\/]+)/(.*)") + (set host ((REQUEST match) groupAtIndex:1)) + (set path (+ "/" ((REQUEST match) groupAtIndex:2))) + ((REQUEST nunja) resolveDomainName:host andDo: + (do (address) + (if address + (then ((REQUEST nunja) getResourceFromHost:host address:address port:80 path:path andDo: + (do (data) + (if data + (then (REQUEST respondWithData:data)) + (else (REQUEST respondWithString:"unable to load #{path}")))))) + (else (REQUEST respondWithString:"unable to resolve host #{host}"))))) + nil) ;; return nil to leave the connection open + ) -(get (regex -"/posttest") +(get "/posttest" (set host "localhost") (set path "/login") ((REQUEST nunja) resolveDomainName:host andDo: @@ -252,19 +295,16 @@ END forKey:"BODY") (get "/recycle.ico" (REQUEST setValue:"application/icon" forResponseHeader:"Content-Type") - (NSData dataWithContentsOfFile:"sample/public/favicon.ico")) + (NSData dataWithContentsOfFile:"public/favicon.ico")) -(get "/follow/:me" +(get "/follow/me:" (REQUEST setValue:"text/plain" forResponseHeader:"Content-Type") (+ "/follow/" ((REQUEST bindings) "me"))) -(get "/:a/before/:b" +(get "/a:/before/b:" (REQUEST setValue:"text/plain" forResponseHeader:"Content-Type") (+ "/" ((REQUEST bindings) "b") "/after/" ((REQUEST bindings) "a"))) -(get (regex -"^/foo/([^/]+)$") - ((REQUEST match) groupAtIndex:1)) - (get "/get" (set q (REQUEST query)) (set a (((q allKeys) sort) map: @@ -279,6 +319,7 @@ END forKey:"BODY") (+ key ":" (q key))))) (a componentsJoinedByString:",")) - +(get-404 + "Resource Not Found: #{(REQUEST path)}") diff --git a/test/test_markup.nu b/test/test_markup.nu deleted file mode 100644 index 0fa44fd..0000000 --- a/test/test_markup.nu +++ /dev/null @@ -1,19 +0,0 @@ -;; test_markup.nu -;; tests for Nunja markup operator -;; -;; Copyright (c) 2010 Tim Burks, Neon Design Technology, Inc. - -(load "Nunja") - -(class TestMarkup is NuTestCase - - (- testMarkup is - (set &html (NunjaMarkupOperator operatorWithTag:"html" prefix:"\n")) - (set &body (NunjaMarkupOperator operatorWithTag:"body")) - - (assert_equal "" (&body)) - (assert_equal "" (&body incomplete:)) - (assert_equal "" (&body attr:"val")) - (assert_equal "\nhello, world" (&html (&body this:"is" a:"test" "hello," " world"))) - (assert_equal "abc123" (&body (array "a" "b" "c") 123)) - (assert_equal "12." (&body "1" (if (eq 1 1) 2) (if (eq 1 2) 3) ".")))) diff --git a/test/test_salt.nu b/test/test_salt.nu deleted file mode 100644 index 2e2dd56..0000000 --- a/test/test_salt.nu +++ /dev/null @@ -1,15 +0,0 @@ -;; test_salt.nu -;; tests for Nunja password salting helper. -;; -;; Copyright (c) 2007 Tim Burks, Neon Design Technology, Inc. - -(load "Nunja") - -(class TestSalt is NuTestCase - - (- testSalt is - (set salted (Nunja saltedPassword:"secret" withSalt:"sauce")) - ;; golden result obtained with "openssl passwd -1 -salt sauce" - (assert_equal "$1$sauce$ToKwxvX1ZyeiswSSzdPRi0" salted))) - -