· 8 years ago · Apr 09, 2018, 09:30 AM
1<cfcomponent hint="This interceptor provides complete SES and URL mappings support to ColdBox Applications"
2 output="false"
3 extends="coldbox.system.interceptors.SES">
4
5<!------------------------------------------- CONSTRUCTOR ------------------------------------------->
6
7 <cffunction name="configure" access="public" returntype="void" hint="This is where the ses plugin configures itself." output="false" >
8 <cfscript>
9 // STATIC Reserved Keys as needed for cleanups
10 instance.RESERVED_KEYS = "handler,action,view,viewNoLayout,module,moduleRouting";
11 instance.RESERVED_ROUTE_ARGUMENTS = "constraints,pattern,regexpattern,matchVariables,packageresolverexempt,patternParams,valuePairTranslation,ssl,append";
12 // STATIC Valid Extensions
13 instance.VALID_EXTENSIONS = "json,jsont,xml,html,htm,rss";
14
15 // Routes Array
16 instance.routes = ArrayNew(1);
17 // Module Routing Table
18 instance.moduleRoutingTable = structnew();
19 // Loose matching flag for regex matches
20 instance.looseMatching = false;
21 // Flag to enable unique or not URLs
22 instance.uniqueURLs = true;
23 // Enable the interceptor by default
24 instance.enabled = true;
25 // Auto reload configuration file flag
26 instance.autoReload = false;
27 // Detect extensions flag, so it can place a 'format' variable on the rc
28 instance.extensionDetection = true;
29 // Throw an exception when extension detection is invalid or not
30 instance.throwOnInvalidExtension = false;
31 // Initialize the valid extensions to detect
32 instance.validExtensions = instance.VALID_EXTENSIONS;
33
34 // Setting Dependencies
35 instance.handlersPath = getSetting("HandlersPath");
36 instance.handlersExternalLocationPath = getSetting("HandlersExternalLocationPath");
37 instance.modules = getSetting("Modules");
38 instance.eventName = getSetting("EventName");
39 instance.defaultEvent = getSetting("DefaultEvent");
40
41 // Dependencies
42 instance.requestService = getController().getRequestService();
43
44 //Import Configuration
45 importConfiguration();
46
47 // Save the base URL in the application settings
48 setSetting('sesBaseURL', getBaseURL() );
49 setSetting('htmlBaseURL', replacenocase(getBaseURL(),"index.cfm",""));
50 </cfscript>
51 </cffunction>
52
53<!------------------------------------------- INTERCEPTION POINTS ------------------------------------------->
54
55 <!--- Pre execution process --->
56 <cffunction name="preProcess" access="public" returntype="void" hint="This is the route dispatch" output="false" >
57 <!--- ************************************************************* --->
58 <cfargument name="event" required="true" hint="The event object.">
59 <cfargument name="interceptData" required="true" hint="interceptData of intercepted info.">
60 <!--- ************************************************************* --->
61
62 <cfif #isDefined("url.path_info")#>
63
64 <cfset local.MYPATH_INFO = "#replaceNoCase(url.path_info, 'index.cfm', '')#">
65
66 <cfelse>
67
68 <cfset local.MYPATH_INFO = "#cgi.PATH_INFO#">
69
70 </cfif>
71
72 <cfset var prc = event.getCollection(private=true)>
73
74 <cfset temp = structDelete(url, 'PATH_INFO')>
75 <cfset temp = Event.removeValue('PATH_INFO')>
76
77 <cfscript>
78 // Find which route this URL matches
79 var aRoute = "";
80 var key = "";
81 var routedStruct = structnew();
82 var rc = arguments.event.getCollection();
83 var cleanedPaths = getCleanedPaths(rc,arguments.Event);
84 var HTTPMethod = arguments.event.getHTTPMethod();
85
86 // Check if disabled or in proxy mode, if it is, then exit out.
87 if ( NOT instance.enabled OR arguments.event.isProxyRequest() ){ return; }
88
89 //Auto Reload, usually in dev? then reconfigure the interceptor.
90 if( instance.autoReload ){ configure(); }
91
92 // Set that we are in ses mode
93 arguments.event.setIsSES(true);
94
95 // Check for invalid URLs if in strict mode via unique URLs
96 if( instance.uniqueURLs ){
97 checkForInvalidURL( cleanedPaths["pathInfo"] , cleanedPaths["scriptName"], arguments.event );
98 }
99
100 // Extension detection if enabled, so we can do cool extension formats
101 if( instance.extensionDetection ){
102 cleanedPaths["pathInfo"] = detectExtension(cleanedPaths["pathInfo"],arguments.event);
103 }
104
105 // Find a route to dispatch
106 aRoute = findRoute(action=cleanedPaths["pathInfo"],event=arguments.event);
107
108 // Now route should have all the key/pairs from the URL we need to pass to our event object for processing
109 for( key in aRoute ){
110 // Reserved Keys Check, only translate NON reserved keys
111 if( not listFindNoCase(instance.RESERVED_KEYS,key) ){
112 rc[key] = aRoute[key];
113 routedStruct[key] = aRoute[key];
114 }
115 }
116
117 // Create Event To Dispatch if handler key exists
118 if( structKeyExists(aRoute,"handler") ){
119 // If no action found, default to the convention of the framework, must likely 'index'
120 if( NOT structKeyExists(aRoute,"action") ){
121 aRoute.action = getDefaultFrameworkAction();
122 }
123 // else check if using HTTP method actions via struct
124 else if( isStruct(aRoute.action) ){
125 // Verify HTTP method used is valid, else throw exception and 403 error
126 if( structKeyExists(aRoute.action,HTTPMethod) ){
127 aRoute.action = aRoute.action[HTTPMethod];
128 // Send for logging in debug mode
129 if( log.canDebug() ){
130 log.debug("Matched HTTP Method (#HTTPMethod#) to routed action: #aRoute.action#");
131 }
132 }
133 else{
134 getUtil().throwInvalidHTTP(className="SES",
135 detail="The HTTP method used: #HTTPMethod# is not valid for the current executing resource. Valid methods are: #aRoute.action.toString()#",
136 statusText="Invalid HTTP method: #HTTPMethod#",
137 statusCode="405");
138 }
139 }
140 // Create routed event
141 rc[instance.eventName] = aRoute.handler & "." & aRoute.action;
142
143 // Do we have a module?If so, create routed module event.
144 if( len(aRoute.module) ){
145 rc[instance.eventName] = aRoute.module & ":" & rc[instance.eventName];
146 }
147
148 }// end if handler exists
149
150 // See if View is Dispatched
151 if( structKeyExists(aRoute,"view") ){
152 // Dispatch the View
153 arguments.event.setView(name=aRoute.view,noLayout=aRoute.viewNoLayout);
154 arguments.event.noExecution();
155 }
156
157 // Save the Routed Variables so event caching can verify them
158 arguments.event.setRoutedStruct(routedStruct);
159
160 // Execute Cache Test now that routing has been done. We override, because events are determined until now.
161 instance.requestService.eventCachingTest(context=arguments.event);
162 </cfscript>
163 </cffunction>
164
165<!------------------------------------------- PUBLIC ------------------------------------------->
166
167 <!--- addModuleRoutes --->
168 <cffunction name="addModuleRoutes" output="false" access="public" returntype="void" hint="Add modules routes in the specified position">
169 <cfargument name="pattern" type="string" required="true" hint="The pattern to match against the URL." />
170 <cfargument name="module" type="string" required="true" hint="The module to load routes for"/>
171 <cfargument name="append" type="boolean" required="false" default="true" hint="Whether the module entry point route should be appended or pre-pended to the array. By default we append to the end of the array"/>
172 <cfscript>
173 var mConfig = instance.modules;
174 var routingTable = getModulesRoutingTable();
175 var x = 1;
176 var args = structnew();
177
178 // Verify module exists and loaded
179 if( NOT structKeyExists(mConfig,arguments.module) ){
180 $throw(message="Error loading module routes as the module requested '#arguments.module#' is not loaded.",
181 detail="The loaded modules are: #structKeyList(mConfig)#",
182 type="SES.InvalidModuleName");
183 }
184
185 // Create the module routes container if it does not exist already
186 if( NOT structKeyExists(routingTable, arguments.module) ){
187 routingTable[arguments.module] = arraynew(1);
188 }
189
190 // Store the entry point for the module routes.
191 addRoute(pattern=arguments.pattern,moduleRouting=arguments.module,append=arguments.append);
192
193 // Iterate through module routes and process them
194 for(x=1; x lte ArrayLen(mConfig[arguments.module].routes); x=x+1){
195 args = mConfig[arguments.module].routes[x];
196 args.module = arguments.module;
197 addRoute(argumentCollection=args);
198 }
199
200 </cfscript>
201 </cffunction>
202
203 <!--- Add a new Route --->
204 <cffunction name="addRoute" access="public" returntype="void" hint="Adds a route to dispatch" output="false">
205 <!--- ************************************************************* --->
206 <cfargument name="action" type="any" required="false" hint="The action in a handler to execute if a pattern is matched. This can also be a structure or JSON structured based on the HTTP method(GET,POST,PUT,DELETE). ex: {GET:'show', PUT:'update', DELETE:'delete', POST:'save'}">
207 <cfargument name="handler" type="string" required="false" hint="The handler to execute if pattern matched.">
208 <cfargument name="module" type="string" required="true" default="" hint="The module to add this route to"/>
209 <cfargument name="orderby" type="string" required="false" hint="The pattern to match against the URL." />
210 <cfargument name="pattern" type="string" required="true" hint="The pattern to match against the URL." />
211 <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">
212 <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">
213 <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.">
214 <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.">
215 <cfargument name="valuePairTranslation" type="boolean" required="false" default="true" hint="Activate convention name value pair translations or not. Turned on by default">
216 <cfargument name="constraints" type="any" required="false" default="" hint="A structure or JSON structure of regex constraint overrides for variable placeholders. The key is the name of the variable, the value is the regex to try to match."/>
217 <cfargument name="moduleRouting" type="string" required="false" default="" hint="Called internally by addModuleRoutes to add a module routing route."/>
218 <cfargument name="ssl" type="boolean" required="true" default="false" hint="Makes the route an SSL only route if true, else it can be anything. If an ssl only route is hit without ssl, the interceptor will redirect to it via ssl"/>
219 <cfargument name="append" type="boolean" required="false" default="true" hint="Whether the module route should be appended or pre-pended to the array. By default we append to the end of the array"/>
220 <!--- ************************************************************* --->
221 <cfscript>
222 var thisRoute = structNew();
223 var thisPattern = "";
224 var thisPatternParam = "";
225 var arg = 0;
226 var x =1;
227 var thisRegex = 0;
228 var oJSON = getPlugin("JSON");
229 var jsonRegex = "^(\{|\[)(.)+(\}|\])$";
230 var patternType = "";
231
232 // Process all incoming arguments into the route to store
233 for(arg in arguments){
234 if( structKeyExists(arguments,arg) ){ thisRoute[arg] = arguments[arg]; }
235 }
236
237 // Process actions as a JSON structure?
238 if( structKeyExists(arguments,"action") AND isSimpleValue(arguments.action) AND reFindnocase(jsonRegex,arguments.action) ){
239 try{
240 // Inflate action to structure
241 thisRoute.action = oJSON.decode(arguments.action);
242 }
243 catch(Any e){
244 $throw("Invalid JSON action","The action #arguments.action# is not valid JSON","SES.InvalidJSONAction");
245 }
246 }
247
248 // Cleanup Route: Add trailing / to make it easier to parse
249 if( right(thisRoute.pattern,1) IS NOT "/" ){
250 thisRoute.pattern = thisRoute.pattern & "/";
251 }
252 // Cleanup initial /, not needed if found.
253 if( left(thisRoute.pattern,1) IS "/" ){
254 if( thisRoute.pattern neq "/" ){
255 thisRoute.pattern = right(thisRoute.pattern,len(thisRoute.pattern)-1);
256 }
257 }
258
259 // Check if we have optional args by looking for a ?
260 if( findnocase("?",thisRoute.pattern) AND NOT findNoCase("regex:",thisRoute.pattern) ){
261 processRouteOptionals(thisRoute);
262 return;
263 }
264
265 // Process json constraints?
266 thisRoute.constraints = structnew();
267 // Check if implicit struct first, else try to do JSON conversion.
268 if( isStruct(arguments.constraints) ){ thisRoute.constraints = arguments.constraints; }
269 else if( reFindnocase(jsonRegex,arguments.constraints) ){
270 try{
271 // Inflate constratints to structure
272 thisRoute.constraints = oJSON.decode(arguments.constraints);
273 }
274 catch(Any e){
275 $throw("Invalid JSON constraints","The constraints #arguments.constraints# is not valid JSON","SES.InvalidJSONConstraint");
276 }
277 }
278
279 // Init the matching variables
280 thisRoute.regexPattern = "";
281 thisRoute.patternParams = arrayNew(1);
282
283 // Check for / pattern
284 if( len(thisRoute.pattern) eq 1){
285 thisRoute.regexPattern = "/";
286 }
287
288 // Process the route as a regex pattern
289 for(x=1; x lte listLen(thisRoute.pattern,"/");x=x+1){
290
291 // Pattern and Pattern Param
292 thisPattern = listGetAt(thisRoute.pattern,x,"/");
293 thisPatternParam = replace(listFirst(thisPattern,"-"),":","");
294
295 // Detect Optional Types
296 patternType = "alphanumeric";
297 if( findnoCase("-numeric",thisPattern) ){ patternType = "numeric"; }
298 if( findnoCase("-alpha",thisPattern) ){ patternType = "alpha"; }
299 if( findNoCase("regex:",thisPattern) ){ patternType = "regex"; }
300
301 // Pattern Type Regex
302 switch(patternType){
303 // CUSTOM REGEX
304 case "regex" : {
305 thisRegex = replacenocase(thisPattern,"regex:","");
306 break;
307 }
308
309 // ALPHANUMERICAL OPTIONAL
310 case "alphanumeric" : {
311 if( find(":",thisPattern) ){
312 thisRegex = "(" & REReplace(thisPattern,":(.[^-]*)","[^/]");
313 // Check Digits Repetions
314 if( find("{",thisPattern) ){
315 thisRegex = listFirst(thisRegex,"{") & "{#listLast(thisPattern,"{")#)";
316 arrayAppend(thisRoute.patternParams,replace(listFirst(thisPattern,"{"),":",""));
317 }
318 else{
319 thisRegex = thisRegex & "+?)";
320 arrayAppend(thisRoute.patternParams,thisPatternParam);
321 }
322 // Override Constraints with your own REGEX
323 if( structKeyExists(thisRoute.constraints,thisPatternParam) ){
324 thisRegex = thisRoute.constraints[thisPatternParam];
325 }
326 }
327 else{
328 thisRegex = thisPattern;
329 }
330 break;
331 }
332 // NUMERICAL OPTIONAL
333 case "numeric" : {
334 // Convert to Regex Pattern
335 thisRegex = "(" & REReplace(thisPattern, ":.*?-numeric", "[0-9]");
336 // Check Digits
337 if( find("{",thisPattern) ){
338 thisRegex = listFirst(thisRegex,"{") & "{#listLast(thisPattern,"{")#)";
339 }
340 else{
341 thisRegex = thisRegex & "+?)";
342 }
343 // Add Route Param
344 arrayAppend(thisRoute.patternParams,thisPatternParam);
345 break;
346 }
347 // ALPHA OPTIONAL
348 case "alpha" : {
349 // Convert to Regex Pattern
350 thisRegex = "(" & REReplace(thisPattern, ":.*?-alpha", "[a-zA-Z]");
351 // Check Digits
352 if( find("{",thisPattern) ){
353 thisRegex = listFirst(thisRegex,"{") & "{#listLast(thisPattern,"{")#)";
354 }
355 else{
356 thisRegex = thisRegex & "+?)";
357 }
358 // Add Route Param
359 arrayAppend(thisRoute.patternParams,thisPatternParam);
360 break;
361 }
362 } //end pattern type detection switch
363
364 // Add Regex Created To Pattern
365 thisRoute.regexPattern = thisRoute.regexPattern & thisRegex & "/";
366
367 } // end looping of pattern optionals
368
369 // Add it to the routing map table
370 if( len(arguments.module) ){
371 // Append or PrePend
372 if( arguments.append ){ ArrayAppend(getModuleRoutes(arguments.module), thisRoute); }
373 else{ arrayPrePend(getModuleRoutes(arguments.module), thisRoute); }
374 }
375 else{
376 // Append or PrePend
377 if( arguments.append ){ ArrayAppend(getRoutes(), thisRoute); }
378 else{ arrayPrePend(getRoutes(), thisRoute); }
379 }
380
381 </cfscript>
382 </cffunction>
383
384 <!--- Get AutoReload --->
385 <cffunction name="getAutoReload" access="public" returntype="any" output="false" hint="Set to auto reload the rules in each request" colddoc:generic="boolean">
386 <cfreturn instance.autoReload>
387 </cffunction>
388 <cffunction name="setAutoReload" access="public" returntype="void" output="false" hint="Get the auto reload flag.">
389 <cfargument name="autoReload" required="true" colddoc:generic="boolean">
390 <cfset instance.autoReload = arguments.autoReload>
391 </cffunction>
392
393 <!--- Getter/Setter for uniqueURLs --->
394 <cffunction name="setUniqueURLs" access="public" output="false" returntype="void" hint="Set the uniqueURLs property">
395 <cfargument name="uniqueURLs" required="true" colddoc:generic="boolean"/>
396 <cfset instance.uniqueURLs = arguments.uniqueURLs />
397 </cffunction>
398 <cffunction name="getUniqueURLs" access="public" output="false" returntype="any" hint="Get uniqueURLs" colddoc:generic="boolean">
399 <cfreturn instance.uniqueURLs/>
400 </cffunction>
401
402 <!--- Setter/Getter for Base URL --->
403 <cffunction name="setBaseURL" access="public" output="false" returntype="void" hint="Set the base URL for the application.">
404 <cfargument name="baseURL" type="string" required="true" />
405 <cfset instance.baseURL = arguments.baseURL />
406 </cffunction>
407 <cffunction name="getBaseURL" access="public" output="false" returntype="string" hint="Get BaseURL">
408 <cfreturn instance.BaseURL/>
409 </cffunction>
410
411 <!--- Get/set Loose Matching --->
412 <cffunction name="getLooseMatching" access="public" returntype="any" output="false" hint="Get the current loose matching property" colddoc:generic="boolean">
413 <cfreturn instance.looseMatching>
414 </cffunction>
415 <cffunction name="setLooseMatching" access="public" returntype="void" output="false" hint="Set the loose matching property of the interceptor">
416 <cfargument name="looseMatching" required="true" colddoc:generic="boolean">
417 <cfset instance.looseMatching = arguments.looseMatching>
418 </cffunction>
419
420 <!--- get/set Extension Detection --->
421 <cffunction name="getExtensionDetection" access="public" returntype="any" output="false" hint="Get the flag if extension detection is enabled" colddoc:generic="boolean">
422 <cfreturn instance.extensionDetection>
423 </cffunction>
424 <cffunction name="setExtensionDetection" access="public" returntype="void" output="false" hint="Call it to activate/deactivate automatic extension detection">
425 <cfargument name="extensionDetection" required="true" colddoc:generic="boolean">
426 <cfset instance.extensionDetection = arguments.extensionDetection>
427 </cffunction>
428
429 <!--- get/set on Invalid Extension --->
430 <cffunction name="getThrowOnInvalidExtension" access="public" returntype="any" output="false" hint="Get if we are throwing or not on invalid extension detection" colddoc:generic="boolean">
431 <cfreturn instance.throwOnInvalidExtension>
432 </cffunction>
433 <cffunction name="setThrowOnInvalidExtension" access="public" returntype="void" output="false" hint="Configure the interceptor to throw an exception or not when invalid extensions are detected">
434 <cfargument name="throwOnInvalidExtension" required="true" colddoc:generic="boolean">
435 <cfset instance.throwOnInvalidExtension = arguments.throwOnInvalidExtension>
436 </cffunction>
437
438 <!--- setValidExtensions --->
439 <cffunction name="setValidExtensions" output="false" access="public" returntype="void" hint="Setup the list of valid extensions to detect automatically for you.: e.g.: json,xml,rss">
440 <cfargument name="validExtensions" required="true" hint="A list of valid extensions to allow in a request"/>
441 <cfset instance.validExtensions = arguments.validExtensions>
442 </cffunction>
443
444 <!--- getValidExtensions --->
445 <cffunction name="getValidExtensions" output="false" access="public" returntype="any" hint="Get the list of valid extensions this interceptor allows">
446 <cfreturn instance.validExtensions>
447 </cffunction>
448
449 <!--- Getter/Setter Enabled --->
450 <cffunction name="setEnabled" access="public" output="false" returntype="void" hint="Set whether the interceptor is enabled or not.">
451 <cfargument name="enabled" required="true" colddoc:generic="boolean"/>
452 <cfset instance.enabled = arguments.enabled />
453 </cffunction>
454 <cffunction name="getEnabled" access="public" output="false" returntype="any" hint="Get enabled" colddoc:generic="boolean">
455 <cfreturn instance.enabled/>
456 </cffunction>
457
458 <!--- Getter routes --->
459 <cffunction name="getRoutes" access="public" output="false" returntype="any" hint="Get the array containing all the routes" colddoc:generic="array">
460 <cfreturn instance.Routes/>
461 </cffunction>
462
463 <!--- getModulesRoutingTable --->
464 <cffunction name="getModulesRoutingTable" output="false" access="public" returntype="any" hint="Get the entire modules routing table" colddoc:generic="struct">
465 <cfreturn instance.moduleRoutingTable>
466 </cffunction>
467
468 <!--- removeModuleRoutes --->
469 <cffunction name="removeModuleRoutes" output="false" access="public" returntype="void" hint="Remove a module's routing table and registration points">
470 <cfargument name="module" required="true" default="" hint="The name of the module to remove"/>
471 <cfscript>
472 var routeLen = arrayLen( instance.routes );
473 var x = 1;
474 var toDelete = arrayNew(1);
475
476 // remove all module routes
477 structDelete(instance.moduleRoutingTable, arguments.module);
478 // remove module routing entry point
479 for(x=1; x lte routeLen; x=x+1){
480 if( instance.routes[x].moduleRouting eq arguments.module ){
481 // store position to delete
482 arrayAppend(toDelete, x);
483 }
484 }
485 // Remove positions from routing.
486 for(x=1; x lte arrayLen(toDelete); x=x+1){
487 arrayDeleteAt(instance.routes, toDelete[x]);
488 }
489 </cfscript>
490 </cffunction>
491
492 <!--- getModuleRoutes --->
493 <cffunction name="getModuleRoutes" output="false" access="public" returntype="any" hint="Get a modules routes array" colddoc:generic="array">
494 <cfargument name="module" required="true" default="" hint="The name of the module"/>
495 <cfscript>
496 var table = getModulesRoutingTable();
497 if( structKeyExists(table, arguments.module) ){
498 return table[arguments.module];
499 }
500 $throw(message="Module routes for #arguments.module# do not exists", detail="Loaded module routes are #structKeyList(table)#",type="SES.InvalidModuleException");
501 </cfscript>
502 </cffunction>
503
504<!------------------------------------------- PRIVATE ------------------------------------------->
505
506 <!--- detectExtension --->
507 <cffunction name="detectExtension" output="false" access="private" returntype="any" hint="Detect extensions from the incoming request">
508 <cfargument name="requestString" required="true" hint="The requested URL string">
509 <cfargument name="event" required="true" hint="The event object.">
510 <cfscript>
511 var extension = listLast(arguments.requestString,".");
512 var extensionLen = len(extension);
513
514 // cleanup of extension, just in case rewrites add garbage.
515 extension = reReplace(extension, "/$","","all" );
516
517 // check if extension found
518 if( listLen(arguments.requestString,".") GT 1 AND len(extension) AND NOT find("/",extension)){
519 // Check if extension is valid?
520 if( listFindNoCase(instance.validExtensions, extension) ){
521 // set the format request collection variable
522 event.setValue("format", lcase(extension));
523 // debug logging
524 if( log.canDebug() ){
525 log.debug("Extension: #lcase(extension)# detected and set in rc.format");
526 }
527 // remove it from the string and return string for continued parsing.
528 return left(requestString, len(arguments.requestString) - extensionLen - 1 );
529 }
530 else{
531 // log invalid extension
532 if( log.canWarn() ){
533 log.warn("Invalid Extension Detected: #lcase(extension)# detected but it is not in the valid extension list: #instance.validExtensions#");
534 }
535 // throw exception if enabled, else just continue
536 if( instance.throwOnInvalidExtension ){
537 getUtil().throwInvalidHTTP(className="SES",
538 detail="Invalid Request Format Extension Detected: #lcase(extension)#. Valid extensions are: #instance.validExtensions#",
539 statusText="Invalid Requested Format Extension: #lcase(extension)#",
540 statusCode="406");
541 }
542 }
543 }
544
545 // return the same request string, extension not found
546 return requestString;
547 </cfscript>
548 </cffunction>
549
550 <!--- setmoduleRoutingTable --->
551 <cffunction name="setModuleRoutingTable" output="false" access="private" returntype="void" hint="Set the module routing table">
552 <cfargument name="routes" required="true" colddoc:generic="struct"/>
553 <cfset instance.moduleRoutingTable = arguments.routes>
554 </cffunction>
555
556 <!--- Set Routes --->
557 <cffunction name="setRoutes" access="private" output="false" returntype="void" hint="Internal override of the routes array">
558 <cfargument name="routes" required="true" colddoc:generic="array"/>
559 <cfset instance.routes = arguments.routes/>
560 </cffunction>
561
562 <!--- Get Default Framework Action --->
563 <cffunction name="getDefaultFrameworkAction" access="private" returntype="string" hint="Get the default framework action" output="false" >
564 <cfreturn getController().getSetting("eventAction",1)>
565 </cffunction>
566
567 <!--- CGI Element Facade. --->
568 <cffunction name="getCGIElement" access="private" returntype="any" hint="The cgi element facade method" output="true" >
569 <cfargument name="cgielement" required="true" hint="The cgi element to retrieve">
570 <cfargument name="Event" required="true" hint="The event object.">
571 <cfscript>
572 // Allow a UDF to manipulate the CGI.PATH_INFO value
573 // in advance of route detection.
574 if (arguments.cgielement is 'path_info') {
575 return local.MYPATH_INFO;
576 } else {
577 return cgi[arguments.cgielement];
578 }
579 return CGI[arguments.CGIElement];
580 </cfscript>
581 </cffunction>
582
583 <!--- Package Resolver --->
584 <cffunction name="packageResolver" access="private" returntype="any" hint="Resolve handler/module packages" output="false" >
585 <!--- ************************************************************* --->
586 <cfargument name="routingString" required="true" hint="The routing string">
587 <cfargument name="routeParams" required="true" hint="The routed params array">
588 <cfargument name="isModule" required="false" default="false" hint="Tells package resolver this is an explicit module package resolving call"/>
589 <!--- ************************************************************* --->
590 <cfscript>
591 var root = instance.handlersPath;
592 var extRoot = instance.handlersExternalLocationPath;
593 var x = 1;
594 var newEvent = "";
595 var thisFolder = "";
596 var foundPaths = "";
597 var routeParamsLen = arrayLen(arguments.routeParams);
598 var rString = arguments.routingString;
599 var returnString = arguments.routingString;
600
601 // Verify if we have a handler on the route params
602 if( findnocase("handler", arrayToList(arguments.routeParams)) ){
603
604 // Cleanup routing string to position of :handler
605 for(x=1; x lte routeParamsLen; x=x+1){
606 if( arguments.routeParams[x] neq "handler" ){
607 rString = replace(rString,listFirst(rString,"/") & "/","");
608 }
609 else{
610 break;
611 }
612 }
613
614 // Now Find Packaging in our stripped rString
615 for(x=1; x lte listLen(rString,"/"); x=x+1){
616
617 // Get Folder from first part of string
618 thisFolder = listgetAt(rString,x,"/");
619
620 // Check if package exists in convention OR external location
621 if( NOT isModule AND
622 (
623 directoryExists(root & "/" & foundPaths & thisFolder)
624 OR
625 ( len(extRoot) AND directoryExists(extRoot & "/" & foundPaths & thisFolder) )
626 )
627 ){
628 // Save Found Paths
629 foundPaths = foundPaths & thisFolder & "/";
630 // Save new Event
631 if(len(newEvent) eq 0){
632 newEvent = thisFolder & ".";
633 }
634 else{
635 newEvent = newEvent & thisFolder & ".";
636 }
637 }//end if folder found
638 // Module check second
639 else if( structKeyExists(instance.modules, thisFolder) ){
640 // Setup the module entry point
641 newEvent = thisFolder & ":";
642 // Change Physical Path to module now, module detected
643 root = instance.modules[thisFolder].handlerPhysicalPath;
644 }
645 else{
646 //newEvent = newEvent & "." & thisFolder;
647 break;
648 }//end not a folder or module
649
650 }//end for loop
651
652 // Replace Return String if new event packaged found
653 if( len(newEvent) ){
654 // module/event replacement
655 returnString = replacenocase(returnString, replace( replace(newEvent,":","/","all") ,".","/","all"), newEvent);
656 }
657 }//end if handler found
658
659 return returnString;
660 </cfscript>
661 </cffunction>
662
663 <cffunction name="serializeURL" access="private" output="false" returntype="any" hint="Serialize a URL when invalid">
664 <!--- ************************************************************* --->
665 <cfargument name="formVars" required="false" default="">
666 <cfargument name="event" required="true">
667 <!--- ************************************************************* --->
668 <cfscript>
669 var vars = arguments.formVars;
670 var key = 0;
671 var rc = arguments.event.getCollection();
672 var resolvedAction = rc.action;
673
674 for(key in rc){
675
676 if (isStruct(action)) {
677
678 for( key in action ){
679 if( not listFindNoCase(instance.RESERVED_KEYS,key) ){
680 resolvedAction = key;
681 }
682 }
683
684 }
685
686 if( NOT ListFindNoCase("route,handler,resolvedAction,#instance.eventName#",key) ){
687 vars = ListAppend(vars, "#lcase(key)#=#rc[key]#", "&");
688 }
689 }
690 if( len(vars) eq 0 ){
691 return "";
692 }
693 return "?" & vars;
694 </cfscript>
695 </cffunction>
696
697 <!--- Check for Invalid URL --->
698 <cffunction name="checkForInvalidURL" access="private" output="false" returntype="void" hint="Check for invalid URL's">
699 <!--- ************************************************************* --->
700 <cfargument name="route" required="true" />
701 <cfargument name="script_name" required="true" />
702 <cfargument name="event" required="true" />
703 <!--- ************************************************************* --->
704 <cfset var handler = "" />
705 <cfset var action = "" />
706 <cfset var newpath = "" />
707 <cfset var httpRequestData = getHttpRequestData()>
708 <cfset var rc = event.getCollection()>
709
710 <!---
711 Verify we have uniqueURLs ON, the event var exists, route is empty or index.cfm
712 AND
713 if the incoming event is not the default OR it is the default via the URL.
714 --->
715 <cfif StructKeyExists(rc, instance.eventName)
716 AND (arguments.route EQ "/index.cfm" or arguments.route eq "")
717 AND (
718 rc[instance.eventName] NEQ instance.defaultEvent
719 OR
720 ( structKeyExists(url,instance.eventName) AND rc[instance.eventName] EQ instance.defaultEvent )
721 )>
722
723 <!--- New Pathing Calculations if not the default event. If default, relocate to the domain. --->
724 <cfif rc[instance.eventName] neq instance.defaultEvent>
725 <!--- Clean for handler & Action --->
726 <cfif StructKeyExists(rc, instance.eventName)>
727 <cfset handler = reReplace(rc[instance.eventName],"\.[^.]*$","") />
728 <cfset action = ListLast( rc[instance.eventName], "." ) />
729 </cfif>
730 <!--- route a handler --->
731 <cfif len(handler)>
732 <cfset newpath = "/" & handler />
733 </cfif>
734 <!--- route path with handler + action if not the default event action --->
735 <cfif len(handler)
736 AND len(action)
737 AND action NEQ getDefaultFrameworkAction()>
738 <cfset newpath = newpath & "/" & action />
739 </cfif>
740 </cfif>
741
742 <!--- Debug Logging --->
743 <cfif log.canDebug()>
744 <cfset log.debug("SES Invalid URL detected. Route: #arguments.route#, script_name: #arguments.script_name#")>
745 </cfif>
746
747 <!--- Relocation headers --->
748 <cfif httpRequestData.method EQ "GET">
749 <cfheader statuscode="301" statustext="Moved permanently" />
750 <cfelse>
751 <cfheader statuscode="303" statustext="See Other" />
752 </cfif>
753
754 <!--- Relocate --->
755 <cfheader name="Location" value="#arguments.event.getSESbaseURL()##newpath##serializeURL(httpRequestData.content,arguments.event)#" />
756 <cfabort />
757 </cfif>
758 </cffunction>
759
760 <!--- Fix Ending IIS funkyness --->
761 <cffunction name="fixIISURLVars" access="private" returntype="any" hint="Clean up some IIS funkyness" output="false" >
762 <cfargument name="requestString" required="true" hint="The request string">
763 <cfargument name="rc" required="true" hint="The request collection">
764 <cfscript>
765 var varMatch = 0;
766 var qsValues = 0;
767 var qsVal = 0;
768 var x = 1;
769
770 // Find a Matching position of IIS ?
771 varMatch = REFind("\?.*=",arguments.requestString,1,"TRUE");
772 if( varMatch.pos[1] ){
773 // Copy values to the RC
774 qsValues = REreplacenocase(arguments.requestString,"^.*\?","","all");
775 // loop and create
776 for(x=1; x lte listLen(qsValues,"&"); x=x+1){
777 qsVal = listGetAt(qsValues,x,"&");
778 arguments.rc[listFirst(qsVal,"=")] = listLast(qsVal,"=");
779 }
780 // Clean the request string
781 arguments.requestString = Mid(arguments.requestString, 1, (varMatch.pos[1]-1));
782 }
783
784 return arguments.requestString;
785 </cfscript>
786 </cffunction>
787
788 <!--- Find a route --->
789 <cffunction name="findRoute" access="private" output="false" returntype="any" hint="Figures out which route matches this request and returns a routed structure">
790 <!--- ************************************************************* --->
791 <cfargument name="action" required="true" hint="The action evaluated by the path_info">
792 <cfargument name="event" required="true" hint="The event object.">
793 <cfargument name="module" required="false" default="" hint="Find a route on a module"/>
794 <!--- ************************************************************* --->
795 <cfset var requestString = arguments.action />
796 <cfset var packagedRequestString = "">
797 <cfset var match = structNew() />
798 <cfset var foundRoute = structNew() />
799 <cfset var params = structNew() />
800 <cfset var key = "" />
801 <cfset var i = 1 />
802 <cfset var x = 1 >
803 <cfset var rc = event.getCollection()>
804 <cfset var _routes = getRoutes()>
805 <cfset var _routesLength = ArrayLen(_routes)>
806
807 <cfscript>
808
809 // Module call? Switch routes
810 if( len(arguments.module) ){
811 _routes = getModuleRoutes(arguments.module);
812 _routesLength = arrayLen(_routes);
813 }
814
815 //Remove the leading slash
816 if( len(requestString) GT 1 AND left(requestString,1) eq "/" ){
817 requestString = right(requestString,len(requestString)-1);
818 }
819 // Add ending slash
820 if( right(requestString,1) IS NOT "/" ){
821 requestString = requestString & "/";
822 }
823
824 // Let's Find a Route, Loop over all the routes array
825 for(i=1; i lte _routesLength; i=i+1){
826
827 // Match The route to request String
828 match = reFindNoCase(_routes[i].regexPattern,requestString,1,true);
829 if( (match.len[1] IS NOT 0 AND getLooseMatching())
830 OR
831 (NOT getLooseMatching() AND match.len[1] IS NOT 0 AND match.pos[1] EQ 1) ){
832 // Setup the found Route
833 foundRoute = _routes[i];
834 // Debug logging
835 if( log.canDebug() ){
836 log.debug("SES Route matched: #foundRoute.toString()# on routed string: #requestString#");
837 }
838 break;
839 }
840
841 }//end finding routes
842
843 // Check if we found a route, else just return empty params struct
844 if( structIsEmpty(foundRoute) ){
845 if( log.canDebug() ){
846 log.debug("No SES routes matched on routed string: #requestString#");
847 }
848 return params;
849 }
850
851 // SSL Checks
852 if( foundRoute.ssl AND NOT event.isSSL()){
853 setNextEvent(uri=cgi.script_name & cgi.path_info,ssl=true,statusCode=302,queryString=cgi.query_string);
854 }
855
856 // Check if the match is a module Routing entry point or not?
857 if( len( foundRoute.moduleRouting ) ){
858
859 // Try to Populate the params from the module pattern if any
860 for(x=1; x lte arrayLen(foundRoute.patternParams); x=x+1){
861 params[foundRoute.patternParams[x]] = mid(requestString, match.pos[x+1], match.len[x+1]);
862 }
863
864 // Save Found URL
865 arguments.event.setValue(name="currentRoutedURL",value=requestString,private=true);
866
867 // Try to discover the route via the module routing calls
868 structAppend(params, findRoute(reReplaceNoCase(requestString,foundRoute.regexpattern,""),arguments.event,foundRoute.moduleRouting), true);
869
870 // Return if parameters found.
871 if( NOT structIsEmpty(params) ){
872 return params;
873 }
874 }
875
876 // Save Found Route
877 arguments.event.setValue(name="currentRoute",value=foundRoute.pattern,private=true);
878 // Save Found URL if NOT Found already
879 if( NOT arguments.event.valueExists(name="currentRoutedURL",private=true) ){
880 arguments.event.setValue(name="currentRoutedURL",value=requestString,private=true);
881 }
882
883 // Do we need to do package resolving
884 if( NOT foundRoute.packageResolverExempt ){
885 // Resolve the packages
886 packagedRequestString = packageResolver(requestString,foundRoute.patternParams, len(arguments.module) GT 0);
887 // reset pattern matching, if packages found.
888 if( compare(packagedRequestString,requestString) NEQ 0 ){
889
890 // Log package resolved
891 if( log.canDebug() ){
892 log.debug("SES Package Resolved: #packagedRequestString#");
893 }
894
895 // Return found Route recursively.
896 return findRoute(action=packagedRequestString,event=arguments.event);
897 }
898 }
899
900 // Populate the params, with variables found in the request string
901 for(x=1; x lte arrayLen(foundRoute.patternParams); x=x+1){
902 params[foundRoute.patternParams[x]] = mid(requestString, match.pos[x+1], match.len[x+1]);
903 }
904
905 // Process Convention Name-Value Pairs
906 if( foundRoute.valuePairTranslation ){
907 findConventionNameValuePairs(requestString,match,params);
908 }
909
910 // Now setup all found variables in the param struct, so we can return
911 for(key in foundRoute){
912 // Check that the key is not a reserved route argument and NOT already routed
913 if( NOT listFindNoCase(instance.RESERVED_ROUTE_ARGUMENTS,key)
914 AND NOT structKeyExists(params, key) ){
915 params[key] = foundRoute[key];
916 }
917 else if (key eq "matchVariables"){
918 for(i=1; i lte listLen(foundRoute.matchVariables); i = i+1){
919 // Check if the key does not exist in the routed params yet.
920 if( NOT structKeyExists(params, listFirst(listGetAt(foundRoute.matchVariables,i),"=") ) ){
921 params[listFirst(listGetAt(foundRoute.matchVariables,i),"=")] = listLast(listGetAt(foundRoute.matchVariables,i),"=");
922 }
923 }
924 }
925 }
926
927 return params;
928 </cfscript>
929 </cffunction>
930
931 <!--- findConventionNameValuePairs --->
932 <cffunction name="findConventionNameValuePairs" access="private" returntype="void" hint="Find the convention name value pairs" output="false" >
933 <cfargument name="requestString" type="string" required="true" hint="The request string">
934 <cfargument name="match" type="any" required="true" hint="The regex matcher">
935 <cfargument name="params" type="struct" required="true" hint="The parameter structure">
936 <cfscript>
937 //var leftOverLen = len(arguments.requestString)-(arguments.match.pos[arraylen(arguments.match.pos)]+arguments.match.len[arrayLen(arguments.match.len)]-1);
938 var leftOverLen = len(arguments.requestString) - arguments.match.len[1];
939 var conventionString = 0;
940 var conventionStringLen = 0;
941 var tmpVar = 0;
942 var i = 1;
943
944 if( leftOverLen gt 0 ){
945 // Cleanup remaining string
946 conventionString = right(arguments.requestString,leftOverLen).split("/");
947 conventionStringLen = arrayLen(conventionString);
948
949 // If conventions found, continue parsing
950 for(i=1; i lte conventionStringLen; i=i+1){
951 if( i mod 2 eq 0 ){
952 // Even: Means Variable Value
953 arguments.params[tmpVar] = conventionString[i];
954 }
955 else{
956 // ODD: Means variable name
957 tmpVar = trim(conventionString[i]);
958 // Verify it is a valid variable Name
959 if ( NOT isValid("variableName",tmpVar) ){
960 tmpVar = "_INVALID_VARIABLE_NAME_POS_#i#_";
961 }
962 else{
963 // Default Value of empty
964 arguments.params[tmpVar] = "";
965 }
966 }
967 }//end loop over pairs
968 }//end if convention name value pairs
969 </cfscript>
970 </cffunction>
971
972 <!--- getCleanedPaths --->
973 <cffunction name="getCleanedPaths" access="private" returntype="any" hint="Get and Clean the path_info and script names structure" output="false" >
974 <cfargument name="rc" required="true" hint="The request collection to incorporate items into"/>
975 <cfargument name="event" required="true" hint="The event object.">
976 <cfscript>
977 var items = structnew();
978
979 // Get path_info & script name
980 items["pathInfo"] = getCGIElement('path_info',arguments.event);
981 items["scriptName"] = trim(reReplacenocase(getCGIElement('script_name',arguments.event),"[/\\]index\.cfm",""));
982
983 // Clean ContextRoots
984 if( len(getContextRoot()) ){
985 items["pathInfo"] = replacenocase(items["pathInfo"],getContextRoot(),"");
986 items["scriptName"] = replacenocase(items["scriptName"],getContextRoot(),"");
987 }
988
989 // Clean up the path_info from index.cfm and nested pathing
990 items["pathInfo"] = trim(reReplacenocase(items["pathInfo"],"[/\\]index\.cfm",""));
991 if( len(items["scriptName"]) ){
992 items["pathInfo"] = replaceNocase(items["pathInfo"],items["scriptName"],'');
993 }
994
995 // clean 1 or > / in front of route in some cases, scope = one by default
996 items["pathInfo"] = reReplaceNoCase(items["pathInfo"], "^/+", "/");
997
998 // fix URL vars after ?
999 items["pathInfo"] = fixIISURLVars(items["pathInfo"],arguments.rc);
1000
1001 return items;
1002 </cfscript>
1003 </cffunction>
1004
1005 <!--- processRouteOptionals --->
1006 <cffunction name="processRouteOptionals" access="private" returntype="void" hint="Process route optionals" output="false" >
1007 <cfargument name="thisRoute" type="struct" required="true" hint="The route struct">
1008 <cfscript>
1009 var x=1;
1010 var thisPattern = 0;
1011 var base = "";
1012 var optionals = "";
1013 var routeList = "";
1014
1015 // Parse our base & optionals
1016 for(x=1; x lte listLen(arguments.thisRoute.pattern,"/"); x=x+1){
1017 thisPattern = listgetAt(arguments.thisRoute.pattern,x,"/");
1018 // Check for ?
1019 if( not findnocase("?",thisPattern) ){
1020 base = base & thisPattern & "/";
1021 }
1022 else{
1023 optionals = optionals & replacenocase(thisPattern,"?","","all") & "/";
1024 }
1025 }
1026 // Register our routeList
1027 routeList = base & optionals;
1028 // Recurse and register in reverse order
1029 for(x=1; x lte listLen(optionals,"/"); x=x+1){
1030 // Create new route
1031 arguments.thisRoute.pattern = routeList;
1032 // Register route
1033 addRoute(argumentCollection=arguments.thisRoute);
1034 // Remove last bit
1035 routeList = listDeleteat(routeList,listlen(routeList,"/"),"/");
1036 }
1037 // Setup the base route again
1038 arguments.thisRoute.pattern = base;
1039 // Register the final route
1040 addRoute(argumentCollection=arguments.thisRoute);
1041 </cfscript>
1042 </cffunction>
1043
1044 <!--- importConfiguration --->
1045 <cffunction name="importConfiguration" output="false" access="private" returntype="void" hint="Import the routing configuration file">
1046 <cfscript>
1047 var appLocPrefix = "/";
1048 var configFilePath = "";
1049 var refLocal = structnew();
1050 var appMapping = getSetting('AppMapping');
1051
1052 // Verify the config file, else set it to our convention in the config/Routes.cfm
1053 if( not propertyExists('configFile') ){
1054 setProperty('configFile','config/Routes.cfm');
1055 }
1056
1057 //App location prefix
1058 if( len(appMapping) ){
1059 appLocPrefix = appLocPrefix & appMapping & "/";
1060 }
1061
1062 // Setup the config Path for relative location first.
1063 configFilePath = appLocPrefix & reReplace(getProperty('ConfigFile'),"^/","");
1064 if( NOT fileExists(expandPath(configFilePath)) ){
1065 //Check absolute location as not found inside our app
1066 configFilePath = getProperty('ConfigFile');
1067 if( NOT fileExists(expandPath(configFilePath)) ){
1068 $throw(message="Error locating routes file: #configFilePath#",type="SES.ConfigFileNotFound");
1069 }
1070 }
1071
1072 // We are ready to roll. Import config to setup the routes.
1073 try{
1074 // Try to remove pathInfoProvider, just in case
1075 structdelete(variables,"pathInfoProvider");
1076 structdelete(this,"pathInfoProvider");
1077 // Include configuration
1078 $include(configFilePath);
1079 }
1080 catch(Any e){
1081 $throw("Error including config file: #e.message# #e.detail#",e.tagContext.toString(),"SES.executingConfigException");
1082 }
1083
1084 // Validate the base URL
1085 if ( len(getBaseURL()) eq 0 ){
1086 $throw('The baseURL property has not been defined. Please define it using the setBaseURL() method.','','interceptors.SES.invalidPropertyException');
1087 }
1088 </cfscript>
1089 </cffunction>
1090
1091 <!--- getUtil --->
1092 <cffunction name="getUtil" access="private" output="false" returntype="any" hint="Create and return a util object" colddoc:generic="coldbox.system.core.util.Util">
1093 <cfreturn CreateObject("component","coldbox.system.core.util.Util")/>
1094 </cffunction>
1095
1096</cfcomponent>