root/coldbox/trunk/system/interceptors/SES.cfc @ 2650

Revision 2650, 32.9 kB (checked in by lmajano, 4 years ago)

Ticket #858
bean factory now allows externa location configuration files

Line 
1<!-----------------------------------------------------------------------
2********************************************************************************
3Copyright 2005-2008 ColdBox Framework by Luis Majano and Ortus Solutions, Corp
4www.coldboxframework.com | www.luismajano.com | www.ortussolutions.com
5********************************************************************************
6
7Author     :    Luis Majano
8Date        :   9/28/2007
9Description :
10        This is an interceptor for ses support. This code is based almost totally on
11        Adam Fortuna's ColdCourse cfc, which is an AMAZING SES component
12        All credits go to him: http://coldcourse.riaforge.com
13----------------------------------------------------------------------->
14<cfcomponent hint="This interceptor provides complete SES and URL mappings support to ColdBox Applications"
15                         output="false"
16                         extends="coldbox.system.Interceptor">
17                                 
18<!------------------------------------------- CONSTRUCTOR ------------------------------------------->
19
20        <cfscript>
21                // Reserved Keys as needed for cleanups
22                instance.RESERVED_KEYS = "handler,action,view,viewNoLayout";
23                instance.RESERVED_ROUTE_ARGUMENTS = "constraints,pattern,regexpattern,matchVariables,packageresolverexempt,patternParams,valuePairTranslation";
24        </cfscript>
25
26        <cffunction name="configure" access="public" returntype="void" hint="This is where the ses plugin configures itself." output="false" >
27                <cfscript>
28                        // Setup the default interceptor properties
29                        setRoutes( ArrayNew(1) );
30                        setLooseMatching(false);
31                        setUniqueURLs(true);
32                        setEnabled(true);
33                        setDebugMode(false);
34                        setAutoReload(false);
35                       
36                        //Import Config
37                        importConfiguration();
38                       
39                        // Save the base URL in the application settings
40                        setSetting('sesBaseURL', getBaseURL() );
41                        setSetting('htmlBaseURL', replacenocase(getBaseURL(),"index.cfm",""));
42                </cfscript>
43        </cffunction>
44       
45
46<!------------------------------------------- INTERCEPTION POINTS ------------------------------------------->
47       
48        <!--- Pre execution process --->
49        <cffunction name="preProcess" access="public" returntype="void" hint="This is the route dispatch" output="false" >
50                <!--- ************************************************************* --->
51                <cfargument name="event"                 required="true" type="any" hint="The event object.">
52                <cfargument name="interceptData" required="true" type="struct" hint="interceptData of intercepted info.">
53                <!--- ************************************************************* --->
54                <cfscript>
55                        /* Find which route this URL matches */
56                        var aRoute = "";
57                        var key = "";
58                        var cleanedPaths = getCleanedPaths();
59                        var routedStruct = structnew();
60                        var rc = event.getCollection();
61                       
62                        // Check if active or in proxy mode
63                        if ( NOT getEnabled() OR arguments.event.isProxyRequest() )
64                                return;
65                       
66                        //Auto Reload?
67                        if( getAutoReload() ){ configure(); }
68                       
69                        // Set that we are in ses mode
70                        arguments.event.setIsSES(true);
71                       
72                        // Check for invalid URLs if in strict mode
73                        if( getUniqueURLs() ){
74                                checkForInvalidURL( cleanedPaths["pathInfo"] , cleanedPaths["scriptName"], arguments.event );
75                        }
76                                               
77                        // Find a route to dispatch
78                        aRoute = findRoute( cleanedPaths["pathInfo"], arguments.event );
79                       
80                        // Now route should have all the key/pairs from the URL we need to pass to our event object
81                        for( key in aRoute ){
82                                // Reserved Keys Check, only translate NON reserved keys
83                                if( not listFindNoCase(instance.RESERVED_KEYS,key) ){
84                                        rc[key] = aRoute[key];
85                                        routedStruct[key] = aRoute[key];
86                                }
87                        }
88                       
89                        // Create Event To Dispatch if handler key exists
90                        if( structKeyExists(aRoute,"handler") ){
91                                // If no action found, default to the convention of the framework, must likely 'index'
92                                if( NOT structKeyExists(aRoute,"action") ){
93                                        aRoute.action = getDefaultFrameworkAction();
94                                }
95                                // else check if using HTTP method actions via struct
96                                else if( isStruct(aRoute.action) ){
97                                        // Verify HTTP method used is valid, else throw exception and 403 error
98                                        if( structKeyExists(aRoute.action,event.getHTTPMethod()) ){
99                                                aRoute.action = aRoute.action[event.getHTTPMethod()];
100                                                if( getDebugMode() ){
101                                                        getPlugin("Logger").debug("SES. Matched HTTP Method (#event.getHTTPMethod()#) to Action: #aRoute.action#");                                     
102                                                }
103                                        }
104                                        else{
105                                                throwInvalidHTTP("The HTTP method used: #event.getHTTPMethod()# is not valid for the current executing event.");
106                                        }
107                                }
108                                // Create event
109                                rc[getSetting('EventName')] = aRoute.handler & "." & aRoute.action;
110                        }
111                       
112                        // See if View is Dispatched
113                        if( structKeyExists(aRoute,"view") ){
114                                // Dispatch the View
115                                arguments.event.setView(name=aRoute.view,noLayout=aRoute.viewNoLayout);
116                                arguments.event.noExecution();
117                        }
118                       
119                        // Save the Routed Variables so event caching can verify them
120                        arguments.event.setRoutedStruct(routedStruct);
121                       
122                        // Execute Cache Test now that routing has been done. We override, because events are determined until now.
123                        getController().getRequestService().EventCachingTest(context=arguments.event);
124                </cfscript>
125        </cffunction>
126
127<!------------------------------------------- PUBLIC ------------------------------------------->
128       
129        <!--- AddCourse --->
130        <cffunction name="addCourse" returntype="void" access="public" hint="@Deprecated, please use addRoute as this method will be removed eventually." output="false">
131                <cfargument name="pattern"                               type="string"  required="true"  hint="The pattern to match against the URL." />
132                <cfargument name="handler"                               type="string"  required="false" hint="The handler to execute if pattern matched.">
133                <cfargument name="action"                                type="string"  required="false" hint="The action in a handler to execute if a pattern is matched.">
134                <cfargument name="packageResolverExempt" type="boolean" required="false" default="false" hint="If this is set to true, then the interceptor will not try to do handler package resolving. Else a package will always be resolved. Only works if :handler is in a pattern">
135                <cfargument name="matchVariables"                type="string"  required="false" hint="A string of name-value pair variables to add to the request collection when this pattern matches. This is a comma delimmitted list. Ex: spaceFound=true,missingAction=onTest">
136                <cfargument name="view"                                  type="string"  required="false" hint="The view to dispatch if pattern matches.  No event will be fired, so handler,action will be ignored.">
137                <cfargument name="viewNoLayout"                  type="boolean" required="false" default="false" hint="If view is choosen, then you can choose to override and not display a layout with the view. Else the view renders in the assigned layout.">
138                <cfargument name="valuePairTranslation"  type="boolean" required="false" default="true"  hint="Activate convention name value pair translations or not. Turned on by default">
139                <cfset addRoute(argumentCollection=arguments)>
140        </cffunction>
141       
142        <!--- Add a new Route --->
143        <cffunction name="addRoute" access="public" returntype="void" hint="Adds a route to dispatch" output="false">
144                <!--- ************************************************************* --->
145                <cfargument name="pattern"                               type="string"  required="true"  hint="The pattern to match against the URL." />
146                <cfargument name="handler"                               type="string"  required="false" hint="The handler to execute if pattern matched.">
147                <cfargument name="action"                                type="string"  required="false" hint="The action in a handler to execute if a pattern is matched.  This can also be a json structure based on the HTTP method(GET,POST,PUT,DELETE). ex: {GET:'show', PUT:'update', DELETE:'delete', POST:'save'}">
148                <cfargument name="packageResolverExempt" type="boolean" required="false" default="false" hint="If this is set to true, then the interceptor will not try to do handler package resolving. Else a package will always be resolved. Only works if :handler is in a pattern">
149                <cfargument name="matchVariables"                type="string"  required="false" hint="A string of name-value pair variables to add to the request collection when this pattern matches. This is a comma delimmitted list. Ex: spaceFound=true,missingAction=onTest">
150                <cfargument name="view"                                  type="string"  required="false" hint="The view to dispatch if pattern matches.  No event will be fired, so handler,action will be ignored.">
151                <cfargument name="viewNoLayout"                  type="boolean" required="false" default="false" hint="If view is choosen, then you can choose to override and not display a layout with the view. Else the view renders in the assigned layout.">
152                <cfargument name="valuePairTranslation"  type="boolean" required="false" default="true"  hint="Activate convention name value pair translations or not. Turned on by default">
153                <cfargument name="constraints"                   type="string"  required="true"  default="" hint="A json map of regex constraint overrides for variable placeholders. The key is the name of the variable, the value is the regex to try to match."/>
154                <!--- ************************************************************* --->
155                <cfscript>
156                var thisRoute = structNew();
157                var thisPattern = "";
158                var thisPatternParam = "";
159                var arg = 0;
160                var x =1;
161                var thisRegex = 0;
162                var oJSON = getPlugin("JSON");
163                var jsonRegex = "^(\{|\[)(.)+(\}|\])$";
164                var patternType = "";
165                       
166                // Process all incoming arguments
167                for(arg in arguments){
168                        if( structKeyExists(arguments,arg) ){ thisRoute[arg] = arguments[arg]; }
169                }
170               
171                // Process json action?
172                if( structKeyExists(arguments,"action") AND reFindnocase(jsonRegex,arguments.action) ){
173                        try{
174                                // Inflate action to structure
175                                thisRoute.action = oJSON.decode(arguments.action);
176                        }
177                        catch(Any e){
178                                $throw("Invalid JSON action","The action #arguments.action# is not valid JSON","SES.InvalidJSONAction");
179                        }
180                }
181               
182                // Add trailing / to make it easier to parse
183                if( right(thisRoute.pattern,1) IS NOT "/" ){
184                        thisRoute.pattern = thisRoute.pattern & "/";
185                }               
186                // Cleanup initial /
187                if( left(thisRoute.pattern,1) IS "/" ){
188                        if( thisRoute.pattern eq "/" ){
189                                $throw(message="Pattern is empty, please verify the pattern is valid. Route: #thisRoute.toString()#",type="SES.InvalidRoute");
190                        }
191                        thisRoute.pattern = right(thisRoute.pattern,len(thisRoute.pattern)-1);
192                }
193               
194                // Check if we have optional args by looking for a ?
195                if( findnocase("?",thisRoute.pattern) ){
196                        processRouteOptionals(thisRoute);
197                        return;
198                }
199               
200                // Process a json constraints?
201                thisRoute.constraints = structnew();
202                if( reFindnocase(jsonRegex,arguments.constraints) ){
203                        try{
204                                // Inflate constratints to structure
205                                thisRoute.constraints = oJSON.decode(arguments.constraints);
206                        }
207                        catch(Any e){
208                                $throw("Invalid JSON constraints","The constraints #arguments.constraints# is not valid JSON","SES.InvalidJSONConstraint");
209                        }
210                }
211               
212               
213                // Init the regexpattern
214                thisRoute.regexPattern = "";
215                thisRoute.patternParams = arrayNew(1);
216                // Process the route as a regex pattern
217                for(x=1; x lte listLen(thisRoute.pattern,"/");x=x+1){
218                       
219                        // Pattern and Pattern Param
220                        thisPattern = listGetAt(thisRoute.pattern,x,"/");
221                        thisPatternParam = replace(listFirst(thisPattern,"-"),":","");
222                       
223                        // Detect Optional Types
224                        patternType = "alphanumeric";
225                        if( findnoCase("-numeric",thisPattern) ){ patternType = "numeric"; }
226                        if( findnoCase("-alpha",thisPattern) ){ patternType = "alpha"; }
227                       
228                        switch(patternType){
229                                // ALPHANUMERICAL OPTIONAL
230                                case "alphanumeric" : {
231                                        if( find(":",thisPattern) ){
232                                                thisRegex = "(" & REReplace(thisPattern,":(.[^-]*)","[^/]");
233                                                // Check Digits Repetions
234                                                if( find("{",thisPattern) ){
235                                                        thisRegex = listFirst(thisRegex,"{") & "{#listLast(thisPattern,"{")#)";
236                                                        arrayAppend(thisRoute.patternParams,replace(listFirst(thisPattern,"{"),":",""));
237                                                }
238                                                else{
239                                                        thisRegex = thisRegex & "+?)";
240                                                        arrayAppend(thisRoute.patternParams,thisPatternParam);
241                                                }
242                                                // Override Constraints with your own REGEX
243                                                if( structKeyExists(thisRoute.constraints,thisPatternParam) ){
244                                                        thisRegex = thisRoute.constraints[thisPatternParam];
245                                                }
246                                        }
247                                        else{
248                                                thisRegex = thisPattern;
249                                        }
250                                        break;
251                                }
252                                // NUMERICAL OPTIONAL
253                                case "numeric" : {
254                                        // Convert to Regex Pattern
255                                        thisRegex = "(" & REReplace(thisPattern, ":.*?-numeric", "[0-9]");
256                                        // Check Digits
257                                        if( find("{",thisPattern) ){
258                                                thisRegex = listFirst(thisRegex,"{") & "{#listLast(thisPattern,"{")#)";
259                                        }
260                                        else{
261                                                thisRegex = thisRegex & "+?)";
262                                        }
263                                        // Add Route Param
264                                        arrayAppend(thisRoute.patternParams,thisPatternParam);
265                                        break;
266                                }
267                                // ALPHA OPTIONAL
268                                case "alpha" : {
269                                        // Convert to Regex Pattern
270                                        thisRegex = "(" & REReplace(thisPattern, ":.*?-alpha", "[a-zA-Z]");
271                                        // Check Digits
272                                        if( find("{",thisPattern) ){
273                                                thisRegex = listFirst(thisRegex,"{") & "{#listLast(thisPattern,"{")#)";
274                                        }
275                                        else{
276                                                thisRegex = thisRegex & "+?)";
277                                        }
278                                        // Add Route Param
279                                        arrayAppend(thisRoute.patternParams,thisPatternParam);
280                                        break;
281                                }
282                        } //end pattern type detection switch
283                       
284                        // Add Regex Created To Pattern
285                        thisRoute.regexPattern = thisRoute.regexPattern & thisRegex & "/";
286                       
287                } // end looping of pattern optionals
288               
289                // Finally add it to the routing table
290                ArrayAppend(getRoutes(), thisRoute);
291                </cfscript>
292        </cffunction>
293       
294        <cffunction name="getAutoReload" access="public" returntype="boolean" output="false" hint="Set to auto reload the rules in each request">
295                <cfreturn instance.autoReload>
296        </cffunction>
297        <cffunction name="setAutoReload" access="public" returntype="void" output="false" hint="Get the auto reload flag.">
298                <cfargument name="autoReload" type="boolean" required="true">
299                <cfset instance.autoReload = arguments.autoReload>
300        </cffunction>
301       
302        <!--- Getter/Setter for uniqueURLs --->
303        <cffunction name="setUniqueURLs" access="public" output="false" returntype="void" hint="Set the uniqueURLs property">
304                <cfargument name="uniqueURLs" type="boolean" required="true" />
305                <cfset instance.uniqueURLs = arguments.uniqueURLs />
306        </cffunction>
307        <cffunction name="getUniqueURLs" access="public" output="false" returntype="boolean" hint="Get uniqueURLs">
308                <cfreturn instance.uniqueURLs/>
309        </cffunction>
310       
311        <!--- Interceptor DebugMode --->
312        <cffunction name="getdebugMode" access="public" output="false" returntype="boolean" hint="Get the current debug mode for the interceptor">
313                <cfreturn instance.debugMode/>
314        </cffunction>
315        <cffunction name="setdebugMode" access="public" output="false" returntype="void" hint="Set the interceptor into debug mode and log all translations">
316                <cfargument name="debugMode" type="boolean" required="true"/>
317                <cfset instance.debugMode = arguments.debugMode/>
318        </cffunction>
319       
320        <!--- Setter/Getter for Base URL --->
321        <cffunction name="setBaseURL" access="public" output="false" returntype="void" hint="Set the base URL for the application.">
322                <cfargument name="baseURL" type="string" required="true" />
323                <cfset instance.baseURL = arguments.baseURL />
324        </cffunction>
325        <cffunction name="getBaseURL" access="public" output="false" returntype="string" hint="Get BaseURL">
326                <cfreturn instance.BaseURL/>
327        </cffunction>
328       
329        <!--- Get/set Loose Matching --->
330        <cffunction name="getLooseMatching" access="public" returntype="boolean" output="false" hint="Get the current loose matching property">
331        <cfreturn instance.looseMatching>
332    </cffunction>
333    <cffunction name="setLooseMatching" access="public" returntype="void" output="false" hint="Set the loose matching property of the interceptor">
334        <cfargument name="looseMatching" type="boolean" required="true">
335        <cfset instance.looseMatching = arguments.looseMatching>
336    </cffunction>
337       
338        <!--- Getter/Setter Enabled --->
339        <cffunction name="setEnabled" access="public" output="false" returntype="void" hint="Set whether the interceptor is enabled or not.">
340                <cfargument name="enabled" type="boolean" required="true" />
341                <cfset instance.enabled = arguments.enabled />
342        </cffunction>
343        <cffunction name="getenabled" access="public" output="false" returntype="boolean" hint="Get enabled">
344                <cfreturn instance.enabled/>
345        </cffunction>
346       
347        <!--- Getter routes --->
348        <cffunction name="getRoutes" access="public" output="false" returntype="Array" hint="Get the array containing all the routes">
349                <cfreturn instance.Routes/>
350        </cffunction>   
351
352<!------------------------------------------- PRIVATE ------------------------------------------->
353       
354        <!--- throwInvalidHTTP --->
355    <cffunction name="throwInvalidHTTP" output="false" access="private" returntype="void" hint="Throw an invalid HTTP exception">
356        <cfargument name="description" type="string" required="true" hint="The throw description"/>
357               
358                <cfheader statuscode="403" statustext="403 Invalid HTTP Method Exception">
359                <cfthrow type="SES.403"
360                             errorcode="403"
361                             message="403 Invalid HTTP Method Exception"
362                                 detail="#arguments.description#">
363                                 
364    </cffunction>
365   
366        <!--- Set Routes --->
367        <cffunction name="setRoutes" access="private" output="false" returntype="void" hint="Internal override of the routes array">
368                <cfargument name="Routes" type="Array" required="true"/>
369                <cfset instance.Routes = arguments.Routes/>
370        </cffunction>
371       
372        <!--- Get Default Framework Action --->
373        <cffunction name="getDefaultFrameworkAction" access="private" returntype="string" hint="Get the default framework action" output="false" >
374                <cfreturn getController().getSetting("eventAction",1)>
375        </cffunction>
376       
377        <!--- CGI Element Facade. --->
378        <cffunction name="getCGIElement" access="private" returntype="string" hint="The cgi element facade method" output="false" >
379                <cfargument name="cgielement" required="true" type="string" hint="The cgi element to retrieve">
380                <cfscript>
381                        return cgi[arguments.cgielement];
382                </cfscript>
383        </cffunction>
384       
385        <!--- Package Resolver --->
386        <cffunction name="packageResolver" access="private" returntype="any" hint="Resolve handler packages" output="false" >
387                <!--- ************************************************************* --->
388                <cfargument name="routingString"        required="true" type="any" hint="The routing string">
389                <cfargument name="routeParams"          required="true" type="any" hint="The route params array">
390                <!--- ************************************************************* --->
391                <cfscript>
392                        var root = getSetting("HandlersPath");
393                        var extRoot = getSetting("HandlersExternalLocationPath");
394                        var x = 1;
395                        var newEvent = "";
396                        var thisFolder = "";
397                        var foundPaths = "";
398                        var rString = arguments.routingString;
399                        var routeParamsLen = ArrayLen(routeParams);
400                        var returnString = arguments.routingString;
401                       
402                        /* Verify if we have a handler on the route params */
403                        if( findnocase("handler", arrayToList(arguments.routeParams)) ){
404                                /* Cleanup routing string to position of :handler */
405                                for(x=1; x lte routeParamsLen; x=x+1){
406                                        if( routeParams[x] neq "handler" ){
407                                                rString = replace(rString,listFirst(rString,"/") & "/","");
408                                        }
409                                        else{
410                                                break;
411                                        }
412                                }       
413                                /* Now Find Packaging in our stripped rString */
414                                for(x=1; x lte listLen(rString,"/"); x=x+1){
415                                        /* Get Folder */
416                                        thisFolder = listgetAt(rString,x,"/");
417                                        /* Check if package exists in convention OR external location */
418                                        if( directoryExists(root & "/" & foundPaths & thisFolder)
419                                                OR
420                                            ( len(extRoot) AND directoryExists(extRoot & "/" & foundPaths & thisFolder) )
421                                            ){
422                                                /* Save Found Paths */
423                                                foundPaths = foundPaths & thisFolder & "/";
424                                                /* Save new Event */
425                                                if(len(newEvent) eq 0){
426                                                        newEvent = thisFolder & ".";
427                                                }
428                                                else{
429                                                        newEvent = newEvent & thisFolder & ".";
430                                                }                                               
431                                        }//end if folder found
432                                        else{
433                                                //newEvent = newEvent & "." & thisFolder;
434                                                break;
435                                        }//end not a folder.
436                                }//end for loop
437                                /* Replace Return String */
438                                if( len(newEvent) ){
439                                        returnString = replacenocase(returnString,replace(newEvent,".","/","all"),newEvent);
440                                }                                       
441                        }//end if handler found
442                       
443                        return returnString;
444                </cfscript>
445        </cffunction>
446       
447        <!--- Serialize a URL --->
448        <cffunction name="serializeURL" access="private" output="false" returntype="string" hint="Serialize a URL">
449                <!--- ************************************************************* --->
450                <cfargument name="formVars" required="false" default="" type="string">
451                <cfargument name="event"        required="true" type="any" hint="The event object.">
452                <!--- ************************************************************* --->
453                <cfscript>
454                        var vars = arguments.formVars;
455                        var key = 0;
456                        var rc = arguments.event.getCollection();
457                       
458                        for(key in rc){
459                                if( NOT ListFindNoCase("route,handler,action,#getSetting('eventName')#",key) ){
460                                        vars = ListAppend(vars, "#lcase(key)#=#rc[key]#", "&");
461                                }
462                        }
463                        if( len(vars) eq 0 ){
464                                return "";
465                        }
466                        else{
467                                return "?" & vars;
468                        }
469                </cfscript>
470        </cffunction>
471       
472        <!--- Check for Invalid URL --->
473        <cffunction name="checkForInvalidURL" access="private" output="false" returntype="void" hint="Check for invalid URL's">
474                <!--- ************************************************************* --->
475                <cfargument name="route"                required="true" type="any" />   
476                <cfargument name="script_name"  required="true" type="any" />
477                <cfargument name="event"                required="true" type="any" hint="The event object.">
478                <!--- ************************************************************* --->
479                <cfset var handler = "" />
480                <cfset var action = "" />
481                <cfset var newpath = "" />
482                <cfset var httpRequestData = "">
483                <cfset var EventName = getSetting('EventName')>
484                <cfset var DefaultEvent = getSetting('DefaultEvent')>
485                <cfset var rc = event.getCollection()>
486               
487                <!--- Get the HTTP Data --->
488                <cfset httpRequestData = GetHttpRequestData()/>
489               
490                <!---
491                Verify we have uniqueURLs ON, the event var exists, route is empty or index.cfm
492                AND
493                if the incoming event is not the default OR it is the default via the URL.
494                --->
495                <cfif StructKeyExists(rc, EventName)
496                          AND (arguments.route EQ "/index.cfm" or arguments.route eq "")
497                          AND (
498                                        rc[EventName] NEQ DefaultEvent
499                                        OR
500                                        ( structKeyExists(url,EventName) AND rc[EventName] EQ DefaultEvent )
501                          )>
502                       
503                        <!--- New Pathing Calculations if not the default event. If default, relocate to the domain. --->
504                        <cfif rc[EventName] neq getSetting('DefaultEvent')>
505                                <!--- Clean for handler & Action --->
506                                <cfif StructKeyExists(rc, EventName)>
507                                        <cfset handler = reReplace(rc[EventName],"\.[^.]*$","") />
508                                        <cfset action = ListLast( rc[EventName], "." ) />
509                                </cfif>
510                                <!--- route a handler --->
511                                <cfif len(handler)>
512                                        <cfset newpath = "/" & handler />
513                                </cfif>
514                                <!--- route path with handler + action if not the default event action --->
515                                <cfif len(handler)
516                                          AND len(action)
517                                          AND action NEQ getDefaultFrameworkAction()>
518                                        <cfset newpath = newpath & "/" & action />
519                                </cfif>
520                        </cfif>
521                        <!--- Debug Mode? --->
522                        <cfif getDebugMode()>
523                                <cfset getPlugin("Logger").debug("SES.Invalid URL detected. Route: #arguments.route#, script_name: #arguments.script_name#")>
524                        </cfif>
525                       
526                        <!--- Relocation headers --->
527                        <cfif httpRequestData.method EQ "GET">
528                                <cfheader statuscode="301" statustext="Moved permanently" />
529                        <cfelse>
530                                <cfheader statuscode="303" statustext="See Other" />
531                        </cfif>
532                        <!--- Relocate --->
533                        <cfheader name="Location" value="#getBaseURL()##newpath##serializeURL(httpRequestData.content,event)#" />
534                        <cfabort />                     
535                </cfif>
536        </cffunction>
537       
538        <!--- Fix Ending IIS funkyness --->
539        <cffunction name="fixIISURLVars" access="private" returntype="string" hint="Clean up some IIS funkyness" output="false" >
540                <cfargument name="requestString"  type="any" required="true" hint="The request string">
541                <cfargument name="rc"                     type="any" required="true" hint="The request collection">
542                <cfscript>
543                        var varMatch = 0;
544                        var qsValues = 0;
545                        var qsVal = 0;
546                        var x = 1;
547                       
548                        // Find a Matching position of IIS ?
549                        varMatch = REFind("\?.*=",arguments.requestString,1,"TRUE");
550                        if( varMatch.pos[1] ){
551                                // Copy values to the RC
552                                qsValues = REreplacenocase(arguments.requestString,"^.*\?","","all");   
553                                // loop and create
554                                for(x=1; x lte listLen(qsValues,"&"); x=x+1){
555                                        qsVal = listGetAt(qsValues,x,"&");
556                                        rc[listFirst(qsVal,"=")] = listLast(qsVal,"=");
557                                }
558                                // Clean the request string
559                                arguments.requestString = Mid(arguments.requestString, 1, (varMatch.pos[1]-1));
560                        }
561                       
562                        return arguments.requestString;
563                </cfscript>
564        </cffunction>
565       
566        <!--- Find a route --->
567        <cffunction name="findRoute" access="private" output="false" returntype="Struct" hint="Figures out which route matches this request">
568                <!--- ************************************************************* --->
569                <cfargument name="action" required="true" type="any" hint="The action evaluated by the path_info">
570                <cfargument name="event"  required="true" type="any" hint="The event object.">
571                <!--- ************************************************************* --->
572                <cfset var requestString = arguments.action />
573                <cfset var packagedRequestString = "">
574                <cfset var match = structNew() />
575                <cfset var foundRoute = structNew() />
576                <cfset var params = structNew() />
577                <cfset var key = "" />
578                <cfset var i = 1 />
579                <cfset var x = 1 >
580                <cfset var rc = event.getCollection()>
581                <cfset var _routes = getRoutes()>
582                <cfset var _routesLength = ArrayLen(_routes)>
583               
584                <cfscript>
585                        // fix URL vars after ?
586                        requestString = fixIISURLVars(requestString,rc);
587                        //Remove the leading slash
588                        if( len(requestString) GT 1 AND left(requestString,1) eq "/" ){
589                                requestString = right(requestString,len(requestString)-1);
590                        }
591                        // Add ending slash
592                        if( right(requestString,1) IS NOT "/" ){
593                                requestString = requestString & "/";
594                        }
595                       
596                        // Let's Find a Route, Loop over all the routes array
597                        for(i=1; i lte _routesLength; i=i+1){
598                                // Match The route to request String
599                                match = reFindNoCase(_routes[i].regexPattern,requestString,1,true);
600                                if( (match.len[1] IS NOT 0 AND getLooseMatching()) OR
601                                    (NOT getLooseMatching() AND match.len[1] IS NOT 0 AND match.pos[1] EQ 1) ){
602                                        // Setup the found Route
603                                        foundRoute = _routes[i];
604                                        // Debug mode?
605                                        if( getDebugMode() ){
606                                                getPlugin("Logger").debug("SES.Route matched: #foundRoute.toString()#");                                       
607                                        }
608                                        break;
609                                }                               
610                        }//end finding routes
611                       
612                        // Check if we found a route, else just return empty params struct
613                        if( structIsEmpty(foundRoute) ){ return params; }
614                       
615                        // Save Found Route
616                        arguments.event.setValue("currentRoute",foundRoute.pattern);
617                       
618                        // Do we need to do package resolving           
619                        if( NOT foundRoute.packageResolverExempt ){
620                                // Resolve the packages
621                                packagedRequestString = packageResolver(requestString,foundRoute.patternParams);
622                                // reset pattern matching, if packages found.
623                                if( compare(packagedRequestString,requestString) NEQ 0 ){
624                                        if( getDebugMode() ){
625                                                getPlugin("Logger").debug("SES.Package Resolved: #packagedRequestString#");                                     
626                                        }
627                                        return findRoute(packagedRequestString,arguments.event);
628                                }
629                        }
630                       
631                        // Populate the params, with variables found in the request string
632                        for(x=1; x lte arrayLen(foundRoute.patternParams); x=x+1){
633                                params[foundRoute.patternParams[x]] = mid(requestString, match.pos[x+1], match.len[x+1]);
634                        }
635                       
636                        // Process Convention Name-Value Pairs
637                        if( foundRoute.valuePairTranslation ){
638                                findConventionNameValuePairs(requestString,match,params);
639                        }
640                       
641                        // Now setup all found variables in the param struct, so we can return
642                        for(key in foundRoute){
643                                if( NOT listFindNoCase(instance.RESERVED_ROUTE_ARGUMENTS,key) ){
644                                        params[key] = foundRoute[key];
645                                }
646                                else if (key eq "matchVariables"){
647                                        for(i=1; i lte listLen(foundRoute.matchVariables); i = i+1){
648                                                params[listFirst(listGetAt(foundRoute.matchVariables,i),"=")] = listLast(listGetAt(foundRoute.matchVariables,i),"=");
649                                        }
650                                }
651                        }
652                       
653                        return params;                 
654                </cfscript>
655        </cffunction>
656       
657        <cffunction name="findConventionNameValuePairs" access="private" returntype="void" hint="Find the convention name value pairs" output="false" >
658                <cfargument name="requestString"        type="string"   required="true" hint="The request string">
659                <cfargument name="match"                        type="any"              required="true" hint="The regex matcher">
660                <cfargument name="params"                       type="struct"   required="true" hint="The parameter structure">
661                <cfscript>
662                //var leftOverLen = len(arguments.requestString)-(arguments.match.pos[arraylen(arguments.match.pos)]+arguments.match.len[arrayLen(arguments.match.len)]-1);
663                var leftOverLen = len(arguments.requestString) - arguments.match.len[1];
664                var conventionString = 0;
665                var conventionStringLen = 0;
666                var tmpVar = 0;
667                var i = 1;
668               
669                if( leftOverLen gt 0 ){
670                        // Cleanup remaining string
671                        conventionString = right(arguments.requestString,leftOverLen);
672                        conventionStringLen = listLen(conventionString,"/");
673                        // If conventions found, continue parsing
674                        if( conventionStringLen gt 1 ){
675                                for(i=1; i lte conventionStringLen; i=i+1){
676                                        if( i mod 2 eq 0 ){
677                                                // Even: Means Variable Value
678                                                arguments.params[tmpVar] = listGetAt(conventionString,i,'/');
679                                        }
680                                        else{
681                                                // ODD: Means variable name
682                                                tmpVar = trim(listGetAt(conventionString,i,'/'));
683                                                // Verify it is a valid variable Name
684                                                if ( NOT isValid("variableName",tmpVar) ){
685                                                        tmpVar = "_INVALID_VARIABLE_NAME_POS_#i#_";
686                                                }
687                                                else{
688                                                        // Default Value of empty
689                                                        arguments.params[tmpVar] = "";
690                                                }
691                                        }
692                                }//end loop over pairs
693                        }//end if at least one pair found
694                }//end if convention name value pairs
695                </cfscript>
696        </cffunction>
697       
698        <cffunction name="getCleanedPaths" access="private" returntype="struct" hint="Get and Clean the path_info and script names" output="false" >
699                <cfscript>
700                        var items = structnew();
701                       
702                        // Get path_info
703                        items["pathInfo"] = getCGIElement('path_info');
704                        items["scriptName"] = trim(reReplacenocase(getCGIElement('script_name'),"[/\\]index\.cfm",""));
705                       
706                        // Clean ContextRoots
707                        if( len(getContextRoot()) ){
708                                items["pathInfo"] = replacenocase(items["pathInfo"],getContextRoot(),"");
709                                items["scriptName"] = replacenocase(items["scriptName"],getContextRoot(),"");
710                        }       
711                        // Clean up the path_info from index.cfm and nested pathing
712                        items["pathInfo"] = trim(reReplacenocase(items["pathInfo"],"[/\\]index\.cfm",""));
713                        // Clean up empty placeholders
714                        items["pathInfo"] = replace(items["pathInfo"],"//","/","all");
715                        if( len(items["scriptName"]) ){
716                                items["pathInfo"] = replaceNocase(items["pathInfo"],items["scriptName"],'');
717                        }
718                       
719                        return items;
720                </cfscript>
721        </cffunction>
722       
723        <cffunction name="processRouteOptionals" access="private" returntype="void" hint="Process route optionals" output="false" >
724                <cfargument name="thisRoute"  type="struct" required="true" hint="The route struct">
725                <cfscript>
726                        var x=1;
727                        var thisPattern = 0;
728                        var base = "";
729                        var optionals = "";
730                        var routeList = "";
731                       
732                        // Parse our base & optionals
733                        for(x=1; x lte listLen(arguments.thisRoute.pattern,"/"); x=x+1){
734                                thisPattern = listgetAt(arguments.thisRoute.pattern,x,"/");
735                                // Check for ?
736                                if( not findnocase("?",thisPattern) ){
737                                        base = base & thisPattern & "/";
738                                }
739                                else{
740                                        optionals = optionals & replacenocase(thisPattern,"?","","all") & "/";
741                                }
742                        }
743                        // Register our routeList
744                        routeList = base & optionals;
745                        // Recurse and register in reverse order
746                        for(x=1; x lte listLen(optionals,"/"); x=x+1){
747                                // Create new route
748                                arguments.thisRoute.pattern = routeList;
749                                // Register route
750                                addRoute(argumentCollection=arguments.thisRoute);       
751                                // Remove last bit
752                                routeList = listDeleteat(routeList,listlen(routeList,"/"),"/");         
753                        }
754                        // Setup the base route again
755                        arguments.thisRoute.pattern = base;
756                        // Register the final route
757                        addRoute(argumentCollection=arguments.thisRoute);
758                </cfscript>
759        </cffunction>
760       
761        <!--- importConfiguration --->
762        <cffunction name="importConfiguration" output="false" access="private" returntype="void" hint="Import the routing configuration file">
763                <cfscript>
764                        var appLocPrefix = "/";
765                        var configFilePath = "";
766                        var local = structnew();
767                       
768                        // Verify the config file, else set it to our convention in the config/Routes.cfm
769                        if( not propertyExists('configFile') ){
770                                setProperty('configFile','config/Routes.cfm');
771                        }                       
772                       
773                        //App location prefix
774                        if( len(getSetting('AppMapping')) ){
775                                appLocPrefix = appLocPrefix & getSetting('AppMapping') & "/";
776                        }
777                       
778                        // Setup the config Path for relative location first.
779                        configFilePath = appLocPrefix & reReplace(getProperty('ConfigFile'),"^/","");
780                        if( NOT fileExists(expandPath(configFilePath)) ){
781                                //Check absolute location as not found inside our app
782                                configFilePath = getProperty('ConfigFile');
783                                if( NOT fileExists(expandPath(configFilePath)) ){
784                                        $throw(message="Error locating routes file: #configFilePath#",type="SES.ConfigFileNotFound");
785                                }       
786                        }
787                       
788                        // We are ready to roll. Import config to setup the routes.
789                        try{
790                                $include(configFilePath);
791                        }
792                        catch(Any e){
793                                $throw("Error including config file: #e.message# #e.detail#",e.tagContext.toString(),"SES.executingConfigException");
794                        }
795                       
796                        // Validate the base URL
797                        if ( len(getBaseURL()) eq 0 ){
798                                $throw('The baseURL property has not been defined. Please define it using the setBaseURL() method.','','interceptors.SES.invalidPropertyException');
799                        }
800                </cfscript>
801        </cffunction>
802
803</cfcomponent>
Note: See TracBrowser for help on using the browser.